重构(笔记)——第十一章

1,096 阅读3分钟

处理概括关系(继承关系)

字段上移

在所有子类中都具有的字段,将其提取到父类中。

函数上移

某些函数,在各个子类中产生完全相同的结果,将其提取到父类中。

构造函数本体上移

将构造函数相同的字段提取到父类中,如果子类有不同的字段需要实例化,在子类中先实例化父类,然后在添加对应的字段。

class Manager extends Employee {
    public Manager(String name, String id, int grade) {
        super(name, id);
        this.grade = grade;
    }
}

上述代码示例中, nameid 为父类共有的字段。

字段下移

与字段上移相反,如果只是某个子类才有的特定字段,应该将其从父类中剥离出来,添加到需要该字段的子类中。

函数下移

与字段下移道理相同,如果该函数只是某个子类才拥有的函数,将该函数从父类剥离出来,添加到对应的子类中。

提炼子类

类中的某些特性只被某些实例用到,那么就可以将一部分用到的特性提取出来,添加到子类中。

提炼超类

两个类以相同的方式做类似的事情,或者以不同的方式做类似的事情。就可以在这中间提取出一个公共的父类,然后继承该父类。

提炼接口

若干客户使用类接口中的同一子集,或者两个类的接口有部分相同。将相同的子集提炼到一个独立接口中。

// 书中的示例
double charge(Employee emp, int days) {
    int base = emp.getRate() * days;
    if (emp.hashSpecialSkill()) {
        return base * 1.05;
    } else {
        return base;
    }
}
// 本例中,只关注 Employee 的 getRate() 和 hashSpecialSkill() 两个功能,而 Employee 可能还会有很多其他功能,但是并不关注它。这样就可以定义一个接口,从而强高“我所需要的功能”

interface Billable {
    int getRate();
    boolean hashSpecialSkill();
}

class Employee implements Billable {
    ...
    // 覆盖接口中的方法    
}

double charge(Billable emp, int days) {
    int base = emp.getRate() * days;
    if (emp.hashSpecialSkill()) {
        return bae * 1.05;
    } else {
        return base;
    }
}

折叠继承体系

通过前面的重构手法,如果产生了某个子类未无价值,那么可以考虑将子类与超类进行合并。

塑模板函数

有一些子类,其中相应的某些函数以相同顺序执行类似的操作,但各个操作的细节上有所不同。将这些操作分别放进独立函数中,并保持它们都有相同的签名,于是原函数也就变得相同了。然后将原函数上移至超类。

public String statement() {
    Enumeration rentals = _rentals.elements();
    String result = "Rental Record for" + getName() +"\n";
    while (rentals.hasMoreElements()) {
        Rental each = (Rental) rentals.nextElement();
        result += "\t" + each.getMove().getTitle() +"\t" + String.valueOf(each.getCharge) + "\n";
    }
    result += "Amount owed is" + getTotalCharge() + "\n";
    result += "You earned" + getTotalFrequentRenterPoints() + " frequent renter points";
    return result;
}

public String htmlStatement() {
    // html 模板输出
}

// 继承体系
class Statement{}
class TextStatement extends Statement {
    public String value(Customer customer) {
        //.... 输出的代码
    }
}
class HtmlStatement extends Statement {
    public String value(Customer customer) {
        //... 输出的代码
    }
}

// 将行为迁移到子类中
class Customer {
    public String Statement() {
        return new TextStatement().value(this);
    }
    
    public String htmlStatement() {
        return new HtmlStatement().value(this);
    }
}

// 按照这个步骤,不断的将重复行为提取
// 最终的形态
class abstract Statement {
    public String value(Customer customer) {
        Enumeration rentals = customer.getRentals();
        String result = hearString(customer);
        while(rentals.hasMoreElements()) {
            Rental each = (Rental) rentals.nextElement();
            result += eachRentalString(each);
        }
        result += footerString(customer);
        return result;
    }
    
    abstract String headerString(Customer customer);
    abstract String eachRentalString(Rental rental);
    abstract String footerString(Customer customer);
}

以委托取代继承

使用成员变量保存某个字段,然后在对外提供的方法中重新调用即可。

class MyStatck extends Vector {
    public void push(Object element) {
        insertElementAt(element, 0);
    }
    
    public Object pop() {
        Object result = firstElement();
        removeElementAt(0);
        return result;
    }
}

// 重构后
class MyStack {
    private Vector verctor = new Vector();
    
    public void push(Object element) {
        vector.insertElementAt(element);
    }
    
    public Object pop() {
        Object result = vector.firstElement();
        vector.removeElementAt(0);
        return result;
    }
    
    public int size() {
        return vector.size();
    }
    
    public boolean isEmpty() {
        return vector.isEmpty();
    }
}

以继承取代委托

与上面的重构手法正好相反,将委托改为使用继承。

作者的两条告诫:

  • 如果你没有使用受委托类的所有函数,那么 就不应该使用“以继承取代委托”
  • 受委托对象不止一个其他对象共享,而且受委托对象是可变的。这种情况也不应该使用“以继承取代委托”