Skip to content

重构 - 11. 重构 API

🏷️ 《重构》

11.1 将查询函数和修改函数分离(Separate Query from Modifier)

如果某个函数只提供一个值,没有任何看得到的副作用,那么这个函数可以任意调用,也很容易测试。

重构前

csharp
decimal GetTotalOutstandingAndSendBill()
{
    var result = _customer.Invoices.Sum(m => m.Amount);
    SendBill();
    return result;
}

重构后

csharp
decimal TotalOutstanding()
{
    return _customer.Invoices.Sum(m => m.Amount);
}

void SendBill()
{
    _emailGateway.Send(FormatBill(_customer));
}

11.2 函数参数化(Parameterize Function)

曾用名另函数携带参数(Parameterize Method)

消除重复,且使函数更有用。

重构前

csharp
void TenPercentRaise(Person aPerson)
{
    aPerson.Salary = decimal.Multiply(aPerson.Salary, new decimal(1.1));
}

void FivePercentRaise(Person aPerson)
{
    aPerson.Salary = decimal.Multiply(aPerson.Salary, new decimal(0.5));
}

重构后

csharp
void Raise(Person aPerson, decimal factor)
{
    aPerson.Salary = decimal.Multiply(aPerson.Salary, factor);
}

11.3 移除标记参数(Remove Flag Argument)

曾用名以明确函数取代参数(Replace Parameter with Explicit Methods)

“标记参数”是这样的一种参数:调用者用它来指示被调函数应该执行哪一部分逻辑。

重构前

csharp
void SetDimension(string name, int value)
{
    if (name == "height")
    {
        _height = value;
    }
    if (name == "width")
    {
        _width = value;
    }
}

重构后

csharp
void SetHeight(int value)
{
    _height = value;
}
void SetWidth(int value)
{
    _width = value;
}

11.4 保持对象完整(Preserve Whole Object)

“传递整个记录”的方式能更好的应对变化,但同时也会导致被调函数依赖于完整对象。

重构前

csharp
var low = aRoom.DaysTempRange.Low;
var high = aRoom.DaysTempRange.High;
if (aPlan.WithinRange(low, high))

重构后

csharp
if (aPlan.WithinRange(aRoom))

11.5 以查询取代参数(Replace Parameter with Query)

曾用名以函数取代参数(Replace Parameter with Method)
反向重构以参数取代查询(Replace Query with Parameter)

参数列表应该尽量避免重复,并且参数列表越短就越容易理解。

重构前

csharp
AvailableVacation(anEmployee, anEmployee.Grade);

void AvailableVacation(Employee anEmployee, int grade)
{
    // calculate vacation
}

重构后

csharp
AvailableVacation(anEmployee);

void AvailableVacation(Employee anEmployee)
{
    var grade = anEmployee.Grade;
    // calculate vacation
}

11.6 以参数取代查询(Replace Query with Parameter)

反向重构以查询取代参数(Replace Parameter with Query)

重构前

csharp
TargetTemperature(aPlan);

private void TargetTemperature(Plan aPlan)
{
    var currentTemperature = thermostat.CurrentTemperature;
    // rest of function
}

重构后

csharp
TargetTemperature(aPlan, thermostat.CurrentTemperature);

private void TargetTemperature(Plan aPlan, double currentTemperature)
{
    // rest of function
}

11.7 移除设值函数(Remove Setting Method)

如果为某个字段提供了设值函数,这就暗示这个字段可以被改变。

如果不希望在对象创建之后此字段还有机会被改变,那就不要为它提供设值函数。

重构前

csharp
class Person
{
    public Person(string name)
    {
        Name = name;
    }
    public string Name { get; set; }
}

重构后

csharp
class Person
{
    public Person(string name)
    {
        Name = name;
    }
    public string Name { get; }
}

11.8 以工厂函数取代构造函数(Replace Constructor with Factory Function)

曾用名以工厂方法取代构造函数(Replace Constructor with Factory Method)

构造函数有一些局限性:只能返回当前类的实例;名字是固定的;需要特殊的操作符来调用(很多语言中是 new 关键字)。工厂函数不受这些限制。

重构前

csharp
var leadEngineer = new Employee(document.LeadEnginner, "E");

重构后

csharp
var leadEngineer = CreateEngineer(document.LeadEnginner);

11.9 以命令取代函数(Replace Function with Command)

曾用名以函数对象取代函数(Replace Method with Method Object)
反向重构以函数取代命令(Replace Command with Function)

将函数封装成自己的对象,有时也是一种有用的办法。这样的对象我称之为“命令对象”(command object),或者简称“命令”(command)。

与普通的函数相比,命令对象提供了更大的控制灵活性和更强的表达能力。

命令的对象的灵活性是以复杂性作为代价的。只有当特别需要命令对象提供的某种能力而普通的函数无法提供这种能力时,才会考虑使用命令对象。

重构前

csharp
int Score(Candidate candidate, MedicalExam medicalExam, ScoringGuide scoringGuide)
{
    int result = 0;
    int healthLevel = 0;
    // long body code
    return result;
}

重构后

csharp
return new Score(candidate, medicalExam, scoringGuide).Execute();

class Score
{
    private Candidate _candidate;
    private MedicalExam _medicalExam;
    private ScoringGuide _scoringGuide;

    public Score(Candidate candidate, MedicalExam medicalExam, ScoringGuide scoringGuide)
    {
        _candidate = candidate;
        _medicalExam = medicalExam;
        _scoringGuide = scoringGuide;
    }

    public int Execute()
    {
        int result = 0;
        int healthLevel = 0;
        // long body code
        return result;
    }
}

11.10 以函数取代命令(Replace Command with Function)

反向重构以命令取代函数(Replace Function with Command)

如果函数不是太复杂,应该考虑将其变回普通函数。

重构前

csharp
class ChargeCalculator
{
    private Customer _customer;
    private decimal _usage;

    public ChargeCalculator(Customer customer, decimal usage)
    {
        _customer = customer;
        _usage = usage;
    }

    decimal Execute()
    {
        return decimal.Multiply(_customer.Rate, _usage);
    }
}

重构后

csharp
decimal Charge(Customer customer, decimal usage)
{
    return decimal.Multiply(customer.Rate, usage);
}

附 1. 引用

  1. 《重构:改善既有代码的设计》 -- 马丁·福勒(Martin Fowler