重构-改善既有代码的设计读书笔记(八)

##重新组织数据

8.1 Self Encapsulate Field (自封装字段)

你直接访问一个字段,但与字段之间的耦合关系逐渐变得笨拙。为这个字段建立取值/设值函数,并且只以这些函数来访问字段。

8.2 Replace Data Value with Object(以对象取代数据值)

你有一个数据项,需要与其他数据和行为一起使用采有意义,将数据项变成对象。
做法:
1. 为待替换数值新建一个类,在其中声明一个final字段,其类型和源类中的待替换数值类型一样。然后在新类中加入这个字段的取值函数,再加上一个接受此字段为参数的构造函数。
2. 将源类中的待替换数值字段的类型改为前面新建的类。
3. 修改源类中该字段的取值函数,令它们调用新类的取值函数。
4. 如果源类构造函数中用到这个待替换字段,我们就修改构造函数,令它改用新类的构造函数来对字段进行赋值动作。
5. 修改源类中待替换字段的设值函数,令它为新类创建一个实例。
6. 现在,你有可能需要对新类使用Change Value to Reference(8.3)。

class Order {   
    public  Order(String customer){
        this.customer = customer;
    }

    public String getCustomer(){
        return customer;
    }

    public void setCustomer(String customer){
        this.customer = customer;
    }

    private String customer;

}

to

class Customer{ 
    public Customer(String name){
        this.name = name;
    }

    public String getName(){
        return name;
    }

    private final String name;
}

class Order {   
    public  Order(String customer){
        this.customer = new Customer(customer);
    }

    public String getCustomer(){
        return customer.getName();
    }

    public void setCustomer(String customer){
        this.customer = new Customer(customer);
    }

    private Customer customer;

}

8.3 Change Value to Reference(将值对象改为引用对象)

你从一个类衍生出许多彼此相等的实例,希望将它们替换为同一个对象。将这个值对象改成引用对象。
做法:
1. 使用Replace Constructor with Factory Method(10.12)。
2. 决定由什么对象负责提供访问新对象的途径。
使用静态字典或一个注册表对象;使用多个对象作为新对象的访问点。
3. 决定这些引用对象应该预先创建好,或是应该动态创建。
4. 修改工厂函数,令它们返回引用对象。

class Customer{ 
    public Customer(String name){
        this.name = name;
    }

    public String getName(){
        return name;
    }

    private final String name;
}

class Order {   
    public  Order(String customer){
        this.customer = new Customer(customer);
    }

    public String getCustomer(){
        return customer.getName();
    }

    public void setCustomer(String customer){
        this.customer = new Customer(customer);
    }

    private Customer customer;

}

to

class Customer{ 
    public static Customer create(String name){
        return (Customer)instance.get(name);
    }

    private Customer(String name){
        this.name = name;
    }

    private static Dictionary instance = new HashTable();

    static void loadCustomer(){
        new Customer("Lemon").store();
        new Customer("Bilston").store();
    }

    private void store(){
        instance.put(this.getName(),this);
    }
}

class Order{    
    public Order(String customer){
        this.customer = Customer.create(customer);
    }
}

8.4 Change Reference to Value(将引用对象改为值对象)

你有一个引用对象,很小且不可变,而且不易管理。将它变成一个值对象。
做法:
1. 检查重构目标是否为不可变对象,或是否可以修改为不可变对象。
如果该对象目前还不是不可变的,就使用Remove Setting Method(10.10),直到它成为不可变的为止。
如果无法将该对象修改为不可变的,就放弃使用本项重构。
2. 建立equals()和hashCode()。
3. 考虑是否可以删除工厂函数,并将构造函数声明为public。

要把一个引用对象变成值对象,关键动作是:检查它是否可变。

8.5 Replace Array with Object(以对象取代数组)

你有一个数组,其中的元素各自代表不同的东西。以对象替换数组,对于数组中的每个元素,以一个字段来表示。

String[] row = new String[3];
row[0] = "Liverpool";
row[1] = "15";

to

Performance row = new Performance();
row.setName("Liverpool");
row.setWins("15");

8.7 Change Unidirectional Association to Bidirectional(将单向关联改为双向关联)

两个都需要使用对方特性,但其间只有一条单向连接。添加一个反向指针,并使修改函数能够同时更新两条连接。
动机:
在开发过程中,你发现被引用者需要得到其引用者以便进行某些操作。
做法:
1. 在被引用类中增加一个字段,用以保存反向指针。
2. 决定由哪个类-引用端还是被引用端-控制关联关系。
2.1 如果两者都是引用对象,而其间的关联是“一对多”,那么就由“拥有单一引用”的那一方承担“控制者”的角色。
2.2 如果某个对象是组成另一对象的部件,那么就由后者负责控制关联关系。
2.3 如果两者都是引用对象,而其间的关联是“多对多”关系,那么随便其中的哪个对象来控制关联关系都无所谓。
3. 在被控端建立一个辅助函数,其命名应该清楚指出它的有限用途。
4. 如果既有的修改函数在控制端,让它负责更新反向指针。
5. 如果既有的修改函数在被控端,就在控制端建立一个控制函数,并让既有的修改函数调用这个新建的控制函数。

class Order{    
    Customer getCustomer(){
        return customer;
    }

    void setCustomer(Customer arg){
        customer = arg;
    }

    Customer customer;

}

class Customer {    
    private Set orders = new HashSet();
}

to

//一对多的情况
class Customer{     
    //辅助函数
    Set friendOrders(){
        return orders;
    }
    //如果希望在Customer中也能修改连接,就调用控制函数
    void addOrder(Order arg){
        arg.setCustomer(this);
    }
}
class Order{    
    void setCustomer(Customer arg){
        //先让对方删除指向你的指针
        if(customer != null)
            customer.friendOrders().remove(this);
        customer = arg;//再将你的指针指向新对象
        //最后让新对象把它的指针指向你
        if(customer != null)
            customer.friendOrders().add(this);
    }
}
//多对多
class Order{    
    void addCustomer(Customer arg){
        arg.friendOrders().add(this);
        customer.add(arg);
    }

    void removeCustomer(Customer arg){
        arg.friendOrders().remove(this);
        customer.remove(arg);
    }
}

class Customer{ 
    void addOrder(Order arg){
        arg.addCustomer(this);
    }

    void removeOrder(Order arg){
        arg.removeCustomer(this);
    }
}

8.8 Change Bidirectional Association to Unidirectional(将双向关联改为单向关联)

两个类之间有双向关联,但其中一个类如今不再需要另一个类的特性,去除不必要的关联。
做法:
1. 找出保存“你想去除的指针”的字段,检查它的每一个用户,判断是否可以去除该指针。
2. 如果客户使用了取值函数,先运用Self Encapsulate Field(8.1)将待删除字段自我封装起来,然后处理取值函数,使它不再调用该字段。
3. 如果客户未使用取值函数,那就直接修改待删除字段的所有被引用点:改以其他途径获得该字段所保存的对象。
4. 如果已经没有任何函数使用待删除字段,移除所有对该字段的更新逻辑,然后移除该字段。

###这两种手法不太理解

8.11 Encapsulate Collection(封装集合)

有个函数返回一个集合,让这个函数返回该集合的一个只读副本,并在这个类中提供添加/移除集合元素的函数。
做法:
1. 加入为集合添加/移除元素的函数。
2. 将保存集合的字段初始化为一个空集合。
3. 找出集合设值函数的所有调用者。你可以修改那个设值函数,让它使用上述新建立的“添加/移除函数”,也可以直接修改调用端,改让它们调用上述新建立的“添加/移除”函数。
4. 找出所有“通过取值函数获得集合并修改其内容的”的函数。逐一修改这些函数,让他们改用“添加/移除”函数。
5. 修改完上述所有的“通过取值函数获得集合并修改集合内容”的函数后,修改取值函数自身,使它返回整个集合的只读副本。
6. 找出取值函数的所有用户,从中找出应该存在于集合所属对象内的代码。运用Extract Method(6.1)和Move Method(7.1)将这些代码移到宿主对象去。
7. 修改现有取值函数的名字,然后添加一个新取值函数,使其返回一个枚举。找出旧取值函数的所有被使用点,将它们都改为使用新取值函数。
8. 如果这一步跨度太大,可以先使用Rename Method修改原取值函数的名称,再建立一个新取值函数用以返回枚举。最后再修改所有调用者,使其调用新取值函数。

class Person{   
    public Set getCourses(){
        return courses;
    }

    public void setCourses(Set arg){
        courses = arg;
    }

    private Set courses;
}

to

class Person{   
    public void addCourse(Course arg){
        courses.add(arg);
    }

    public void removeCourse(Course arg){
        courses.remove(arg);
    }

    private Set courses = new HashSet();
    //可以根据具体情况决定是否使用或移除设值函数。如果使用,设值函数可以调用添加函数初始化集合
    public vodi setCourses(Set arg){
        Iterator it = arg.iterator();
        while(it.hasNext()){
            addCourse((Course)it.next());
        }
    }
    //对于通过取值函数修改集合的情况
    public Set getCourses(){
        return Collections.unmodifiableSet(courses);
    }
}

###实际编码中,从来没见过,也从来没人提过集合的封装这个问题。

8.13 Replace Type Code with Class(以类取代类型码)

类之中有一个数值类型码,但它并不影响类的行为。以一个新的类替换该数值类型码。

class Person{   
    public static final int O = 0;
    public static final int A = 1;
    public static final int B = 2;
    public static final int AB =3;

    private int bloodGroup;

    public Person(int bloodGroup) {
        this.bloodGroup = bloodGroup;
    }

    //set/get方法
}

做法:
1、为类型码建立一个类。
这个类需要一个用以记录类型码的字段,其类型应该和类型码相同,并应该有对应的取值函数。此外还应该用一组静态变量保存运行被创建的实例,并以一个静态函数根据原本的类型码返回合适的实例。

class BloodGroup{   
    public static final BloodGroup O = new BloodGroup(0);
    public static final BloodGroup A = new BloodGroup(1);
    public static final BloodGroup B = new BloodGroup(2);
    public static final BloodGroup AB = new BloodGroup(3);
    public static final BloodGroup[] values = {O,A,B,AB};

    private final int code;

    private BloodGroup(int code){
        this.code = code;
    }

    //get方法

    public static BloodGroup code(int arg){
        return values[arg];
    }
}

2、修改源类实现,让它使用上述新建的类。
维持原先以类型码为基础的函数接口,但改变静态字段,以新建的类产生代码。然后,修改类型码相关函数,让它们也从新建的类中获取类型码。

class Person{   
    public static final int O = BloodGroup.O.getCode();
    public static final int A = BloodGroup.A.getCode();
    public static final int B = BloodGroup.B.getCode();
    public static final int AB =BloodGroup.AB.getCode();

    private BloodGroup bloodGroup;

    public Person(int bloodGroup) {
        this.bloodGroup = BloodGroup.code(bloodGroup);
    }

    public int getBloodGroup(){
        return bloodGroup.getCode();
    }

    pubic void setBloodGroup(int arg){
        this.bloodGroup = BloodGroup.code(arg);
    }
}

3、对于源类中每一个使用类型码的函数,相应建立一个函数,让新函数使用新建的类。
你需要建立“以新类实例为自变量”的函数,用以替换原先“直接以类型码为参数”的函数。你还需要建立一个“返回新类实例”的函数,用以替换原先“直接返回类型码”的函数。建立新函数前,你可以使用Rename Method(10.1)修改原函数名称,明确指出哪些函数仍然使用旧式的类型码。

class Person{   
    public static final int O = BloodGroup.O.getCode();
    public static final int A = BloodGroup.A.getCode();
    public static final int B = BloodGroup.B.getCode();
    public static final int AB =BloodGroup.AB.getCode();

    private BloodGroup bloodGroup;

    public Person(int bloodGroup) {
        this.bloodGroup = BloodGroup.code(bloodGroup);
    }

    public int getBloodGroupCode(){
        return bloodGroup.getCode();
    }
    pubic void setBloodGroup(int arg){
        this.bloodGroup = BloodGroup.code(arg);
    }

    public BloodGroup getBloodGroup(){
        return bloodGroup;
    }
    public Person(BloodGroup arg){
        bloodGroup = arg;
    }

    public void setBloodGroup(BloodGroup arg){
        bloodGroup = arg;
    }
}

4、逐一修改源类用户,让它们使用新接口。

Person thePerson = new Person(Person.A);
thePerson.getBloodGroupCode();
thePerson.setBloodGroup(Person.AB);

to

Person thePerson = new Person(BloodGroup.A);
thePerson.getBloodGroup().getCode();
thePerson.setBloodGroup(BloodGroup.AB);

5、删除使用类型码的旧接口,并删除保存旧类型码的静态变量。
删掉与code相关的get/set方法和静态常量。

8.14 Replace Type Code with Subclasses(以子类取代类型码)

你有一个不可变的类型码,它会影响类的行为。以子类取代这个类型码。
不能使用这种方法的情况:

  • 类型码值周对象创建之后发生了改变。

  • 由于某些原因,类型码宿主类已经有了子类。
    上述情况需要使用Replace Type Code with State/Strategy(8.15)。

class Employee {    
    private int type;
    static final int ENGINEER = 0;
    static final int SALESMAN = 1;
    static final int MANAGER = 2;

    Employee(int type) {
        this.type = type;
    }
}

做法:
1、使用Self Encapsulate Field(8.1)将类型码自我封装起来。

int getType() {
    return type;
}

如果类型码被传递给构造函数,就需要将构造函数换成工厂函数。

static Employee create(int type) {      
    return new Employee(type);
}
private Employee(int type) {    
    this.type = type;
}

2、为类型码的 每一个数值建立一个相应的子类。在每个子类中覆写类型码的取值函数,使其返回相应的类型码值。

class Engineer extends Employee{
    int getType(){
        return Employee.ENGINEER;
    }
}

class Employee{
    static Employee create(int type) {
        if(type == ENGINEER) return new Engineer();
        else return new Employee(type);
    }
}

3、从超类中删掉保存类型码的字段,将类型码访问函数声明为抽象函数。

abstract int getType();
static Employee create(int type) {  
    switch(type){
        case ENGINEER:
            return new Engineer();
        case SALESMAN:
            return new Salesman();
        ...
    }
}

这时还可以使用Push Down Method(11.4)和Push Down Field(11.5),将只与特定种类雇员相关的函数和字段推到相关的 子类去。

8.15 Replace Type Code with State/Strategy(以State/Strategy取代类型码)

你有一个类型码,它会影响类的行为,但你无法通过继承手法消除它。以状态对象取代类型码。
如果你打算在完成本重构之后再以Replace Conditional with Polymorphism(9.6)简化一个算法,那么选择Strategy模式比较合适;如果你打算搬移与状态相关的数据,而且你把新建对象视为一种变迁状态,就应该选择使用State模式。

class Employee{
    private int type;
    private final int ENGINEER = 0;
    private final int SALESMAN = 1;
    private final int MANAGER = 2;

    Employee(int type) {
        this.type = type;
    }

    int payAmount(){
        switch(type){
            case ENGINEER:
                return monthlySalary;
            case SALESMAN:
                return monthlySalary+commission;
            case MANAGER:
                return montyleSalary+bonus;
            ...
        }
    }

}

做法:
1、使用Self Encapsulate Field(8.1)将类型码自我封装起来。
加上对象的类型码可变,那么就不能使用继承方式来处理类型码。

Employee (int type) {   
    setType(type);
}
int getType(){
    return type;
}
void setType(int type) {    
    this.type = type;
}
int payAmount(){
        switch(getType()){
            case ENGINEER:
                return monthlySalary;
            case SALESMAN:
                return monthlySalary+commission;
            case MANAGER:
                return montyleSalary+bonus;
            ...
        }
    }

2、新建一个类,根据类型码的用途为它命名。这就是一个状态对象。

3、为这个新类添加子类,每个子类对应一种类型码。

4、在超类中建立一个抽象的查询函数,用以返回类型码。在每个子类中覆写该函数,返回确切的类型码。

abstract class EmployeeType{
    abstract int getTypeCode();
}

class Engineer extends EmployeeType{
    int getTypeCode(){
        return Employee.ENGINEER;
    }
}
//Manager类和Salesman类
...

5、在源类中建立一个字段,用以保存新建的状态对象。

6、调整源类中负责查询类型码的函数,将查询动作转发给状态对象。

7、调整源类中为类型码设值的函数,将一个恰当的状态对象子类赋值给“保存状态对象”的那个字段。

class Employee{ 
    private EmployeeType type;

    int getType(){
        return type.getTypeCode();
    }

    void setType(int arg){
        switch(arg) {
            case ENGINEER:
                type = new Engineer();
                break;
            case SALESMAN:
                type = new Salesman();
                break;
            case MANAGER:
                type = new Manager();
                break;
            ...
        }
    }
}

之后可以运用Replace Constructor with Factory Method(10.12)针对不同的case子句建立相应的工厂函数。还可以再立刻使用Replace Conditional with Polymorphism(9.6),从而将其他的case消除掉。
可以将所有关于类型码和子类的知识都移到新类。

class Employee{
    void setType(int arg){
        type = EmployeeType.newType(arg);
    }
}

class EmployeeType{
    static EmployeeType newType(int code){
        switch(code){
            case ENGINEER:
                return new Engineer();
            case SALESMAN:
                return new Salesman();
            case MANAGER:
                return new Manager();
            ...
        }
    }

    static final int ENGINEER = 0;
    static final int SALESMAN = 1;
    static final int MANAGER = 2;
}

删掉原始类中的类型码定义,添加一个对新类的引用。

class Employee{ 
    int payAmount(){
        switch(getType()){
            case EmployeeType.ENGINEER:
                return monthlySalary;
            case EmployeeType.SALESMAN:
                return monthlySalary+commission;
            ...
        }
    }
}

最后,可以使用Replace Conditional with Polymorphism(9.6)处理payAmount()函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

每天进步一点_点

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值