重构-改善既有代码的设计完整笔记系列之6、7 - 重新组织函数、对象间搬迁特性

目录

  • 6.1 Extract Method(提炼函数)
  • 6.2 Inline Method(内联函数)
  • 6.3 Inline Temp(内联临时变量)
  • 6.4 Replace Temp with Query(以查询取代临时变量)
  • 6.5 Introduce Explaining Variable(引入解释性变量)
  • 6.6 Split Temporary Variable(分解临时变量)
  • 6.7 Remove Assignments to Parameters(移除对参数的赋值)
  • 6.8 Replace Method with Method Object(以函数对象取代函数)
  • 6.9 Substitute Algorithm(替换算法)
  • 7.1 Move Method(搬移函数)
  • 7.2 Move Field(搬移字段)
  • 7.3 Extract Class(提炼类)
  • 7.4 Inline Class(将类内联化)
  • 7.5 Hide Delegate(隐藏“委托关系”)
  • 7.6 Remove Middle Man(移除中间人)
  • 7.7 Introduce Foreign Method(引入外加函数)
  • 7.8 Introduce Local Extension(引入本地扩展)


6.1 ExtractMethod(提炼函数)

将一段代码放进一个独立函数中,并让函数名称解释该函数的用途。
增加可读性,函数粒度小更容易被复用和覆写。

void printOwing() {
        Enumeration e = _orders.elements();
        double outstanding = 0.0;
        // print banner
        System.out.println("**************************");
        System.out.println("***** Customer Owes ******");
        System.out.println("**************************");
        // calculate outstanding
        while (e.hasMoreElements()) {
            Order each = (Order) e.nextElement();
            outstanding += each.getAmount();
        }
        // print details
        System.out.println("name:" + _name);
        System.out.println("amount" + outstanding);
}
改
    void printOwing(double previousAmount) {
        printBanner();
        double outstanding = getOutstanding(previousAmount * 1.2);
        printDetails(outstanding);
    }

    void printBanner() {
        // print banner
        System.out.println("**************************");
        System.out.println("***** Customer Owes ******");
        System.out.println("**************************");
    }

    double getOutstanding(double initialValue) {
        double result = initialValue;
        Enumeration e = _orders.elements();
        while (e.hasMoreElements()) {
            Order each = (Order) e.nextElement();
            result += each.getAmount();
        }
        return result;
    }

    void printDetails(double outstanding) {
        System.out.println("name:" + _name);
        System.out.println("amount" + outstanding);
    }

6.2 Inline Method(内联函数)

在函数调用点插入函数本体,然后移除该函数。
函数的本体与名称同样清楚易懂,间接层太多反而不易理解。

int getRating() {
    return (moreThanFiveLateDeliveries()) ? 2 : 1;
}

boolean moreThanFiveLateDeliveries() {
    return _numberOfLateDeliveries > 5;
}

改
int getRating() {
    return (_numberOfLateDeliveries > 5) ? 2 : 1;
}


6.3 Inline Temp(内联临时变量)

将所有对该变量的引用动作,替换为对它赋值的那个表达式自身。

double basePrice = anOrder.basePrice();
return (basePrice > 1000)
改
return (anOrder.basePrice() > 1000)

6.4 Replace Temp with Query(以查询取代临时变量)
将一个表达式提炼到一个独立函数中,并将临时变量的引用点替换为对函数的调用。
临时变量扩展为查询函数,就可以将使用范围扩展到整个类。
减少临时变量,使函数更短更易维护。让原来的不容易理解的长函数分段化。

    double getPrice() {
        int basePrice = _quantity * _itemPrice;
        double discountFactor;
        if (basePrice > 1000)
            discountFactor = 0.95;
        else
            discountFactor = 0.98;
        return basePrice * discountFactor;
    }
改

    double getPrice() {
        return basePrice() * discountFactor();
    }

    private double discountFactor() {
        if (basePrice() > 1000)
            return 0.95;
        else
            return 0.98;
    }

    private int basePrice() {
        return _quantity * _itemPrice;
    }

6.5 Introduce Explaining Variable(引入解释性变量)

将该复杂表达式的结果放进一个临时变量,以变量名来解释其用途。让冗长的表达式更加可读。

    double price() {
        // price is base price - quantity discount + shipping
        return _quantity * _itemPrice - Math.max(0, _quantity - 500) * _itemPrice * 0.05
                + Math.min(_quantity * _itemPrice * 0.1, 100.0);
    }

改
    double price() {
        return basePrice() - quantityDiscount() + shipping();
    }

    private double quantityDiscount() {
        return Math.max(0, _quantity - 500) * _itemPrice * 0.05;
    }

    private double shipping() {
        return Math.min(basePrice() * 0.1, 100.0);
    }

    private double basePrice() {
        return _quantity * _itemPrice;
    }

6.6 Split Temporary Variable(分解临时变量)

针对每次赋值,创造一个独立、对应的临时变量。
临时变量会被多次赋值,容易产生理解歧义。
如果变量被多次赋值(除了“循环变量”和“结果收集变量”),说明承担了多个职责,应该分解。

    double getDistanceTravelled (int time) {
        double result;
        double acc = _primaryForce / _mass;
        int primaryTime = Math.min(time, _delay);
        result = 0.5 * acc * primaryTime * primaryTime;
        int secondaryTime = time - _delay;
        if (secondaryTime > 0) {
        double primaryVel = acc * _delay;
            acc = (_primaryForce + _secondaryForce) / _mass;
            result += primaryVel * secondaryTime + 0.5 * acc * secondaryTime * secondaryTime;
        }
        return result;
        }
//acc被赋值了2次,代码比较乱
改
    double getDistanceTravelled(int time) {
        double result;
        final double primaryAcc = _primaryForce / _mass;
        int primaryTime = Math.min(time, _delay);
        result = 0.5 * primaryAcc * primaryTime * primaryTime;
        int secondaryTime = time - _delay;
        if (secondaryTime > 0) {
            double primaryVel = primaryAcc * _delay;
            final double secondaryAcc = (_primaryForce + _secondaryForce) / _mass;
            result += primaryVel * secondaryTime + 0.5 * secondaryAcc * secondaryTime * secondaryTime;
        }
        return result;
    }

使用secondaryAcc变量来区分

6.7 Remove Assignments to Parameters(移除对参数的赋值)

以一个临时变量取代该参数的位置。
对参数赋值容易降低代码的清晰度;
容易混淆按值传递和按引用传递的方式。

    int discount(int inputVal, int quantity, int yearToDate) {
        if (inputVal > 50)
            inputVal -= 2;
        if (quantity > 100)
            inputVal -= 1;
        if (yearToDate > 10000)
            inputVal -= 4;
        return inputVal;
    }

改
    int discount(final int inputVal, final int quantity, final int yearToDate) {
        int result = inputVal;
        if (inputVal > 50)
            result -= 2;
        if (quantity > 100)
            result -= 1;
        if (yearToDate > 10000)
            result -= 4;
        return result;
    }

加上final,让它遵循不对参数赋值。

6.8 Replace Method with Method Object(以函数对象取代函数)

一个大型函数如果包含了很多临时变量,用Extract Method很难拆解,
可以把函数放到一个新创建的类中,把临时变量变成类的实体变量,再用Extract Method拆解。

感觉原书的例子不大贴切,略过。

6.9 Substitute Algorithm(替换算法)

复杂的算法会增加维护的成本,替换成较简单的算法实现,往往能明显提高代码的可读性和可维护性。

string foundPerson(string[] people){
    for (int i = 0; i < people.length; i++){
        if (people[i].equals("Don")){
            return "Don";
        }
        if (people[i].equals("John")){
            return "John";
        }
    }
    return "";
}
重构后
string foundPerson(string[] people){
    list oList = Arrays.asList(new String[]{"Don", "John"});
    for (int i = 0; i < people.length; i++){
        if (oList.contains(people[i]))
            return people[i];
    }
    return "";
}

感觉也有点不大贴切。

7.1 Move Method(搬移函数)

如果一个函数在另一个类中用的更多。在另一个类中添加一个有着类似行为的新函数,将旧函数变成一个单纯的委托函数或者将旧函数删除。
类的行为做到单一职责,不要越俎代庖。
如果一个类有太多行为,或一个类与另一个类有太多合作而形成高度耦合,就需要搬移函数。
观察调用它的那一端、它调用的那一端,已经继承体系中它的任何一个重定义函数。
根据“这个函数不哪个对象的交流比较多”,决定其移动路径。

我不大同意作者将透支函数搬到账户类型类,代码略。

7.2 Move Field(搬移字段)

某个字段被别一个类更多的用到,把这个类移到另一个类中,修改源字段的所有用户,令它们引用新的字段。

class A{
public:
string m_field;
}
class B{

}
重构后

class A{

}
class B{
publuc:
string m_field;
}

7.3 Extract Class(提炼类)

某个类做了应该由两个类做的事,建立一个新类,将相关字段和函数从旧类搬移到新类中。
职责单一,松耦合原则。

class Person {
    public String getName() {
        return _name;
    }

    public String getTelephoneNumber() {
        return ("(" + _officeAreaCode + ") " + _officeNumber);
    }

    String getOfficeAreaCode() {
        return _officeAreaCode;
    }

    void setOfficeAreaCode(String arg) {
        _officeAreaCode = arg;
    }

    String getOfficeNumber() {
        return _officeNumber;
    }

    void setOfficeNumber(String arg) {
        _officeNumber = arg;
    }

    private String _name;
    private String _officeAreaCode;
    private String _officeNumber;
}

改

class Person {
    public String getName() {
        return _name;
    }

    public String getTelephoneNumber() {
        return _officeTelephone.getTelephoneNumber();
    }

    TelephoneNumber getOfficeTelephone() {
        return _officeTelephone;
    }

    private String _name;
    private TelephoneNumber _officeTelephone = new TelephoneNumber();

}

class TelephoneNumber {
    public String getTelephoneNumber() {
        return ("(" + _areaCode + ") " + _number);
    }

    String getAreaCode() {
        return _areaCode;
    }

    void setAreaCode(String arg) {
        _areaCode = arg;
    }

    String getNumber() {
        return _number;
    }

    void setNumber(String arg) {
        _number = arg;
    }

    private String _areaCode;
    private String _number;
}

7.4 Inline Class(将类内联化)

正好于Extract Class (提炼类)相反。如果一个类不再承担足够责任、不再有单独存在的理由。将这个类的所有特性搬移到另一个类中,然后移除原类。

 

class Person {
    Department _department;

    public Department getDepartment() {
        return _department;
    }

    public void setDepartment(Department arg) {
        _department = arg;
    }
}

class Department {
    private String _chargeCode;
    private Person _manager;

    public Department(Person manager) {
        _manager = manager;
    }

    public Person getManager() {
        return _manager;
    }
}

7.5 Hide Delegate(隐藏“委托关系”)

class Person {
    Department _department;

    public Department getDepartment() {
        return _department;
    }

    public void setDepartment(Department arg) {
        _department = arg;
    }
}

class Department {
    private String _chargeCode;
    private Person _manager;

    public Department(Person manager) {
        _manager = manager;
    }

    public Person getManager() {
        return _manager;
    }
}

//如果要获取某个员工的经理:
manager = john.getDepartment().getManager();

//这就暴露了Department的原理,可以在Person类增加方法来减少耦合
public Person getManager() {
    return _department.getManager();
}

//这样就可以直接调用了
manager = john.getManager();

7.6 Remove Middle Man(移除中间人)

某个类做了过多的简单委托动作,让客户直接调用受托类。

A->B->C

重构后

A->B
A->C

7.7 Introduce Foreign Method(引入外加函数)

你需要为提供服务的类增加一个函数,但你无法修改这个类。
在客户类中建立一个函数,并以第一参数形式传入一个服务类实例。
客户类使用Date类的接口,但Date类没有提供nextDay()的接口,也不能改Date的源码:

Date newStart = new Date(pre.getYear(), pre.getMonth(), pre.getDate() + 1);
//重构后
Date newStart = nextDay(pre);

private static Date nextDay(Date arg){
    return new Date(arg.getYear(), arg.getMonth(), arg.getDate() + 1);
}

7.8 Introduce Local Extension(引入本地扩展)

你需要为服务类提供一些额外函数,但你无法修改这个类。
建立一个新类,使它包含这些额外函数。让这个扩展品成为源类的子类戒包装类。

posted @ 2020-03-19 20:17  昕友软件开发  阅读(361)  评论(0编辑  收藏  举报
欢迎访问我的开源项目:xyIM企业即时通讯