重构改善既有代码的设计-学习(六):处理继承关系

1、函数上移(Pull Up Method)

重构改善既有代码的设计-学习(六):处理继承关系_第1张图片 

        无论何时,只要系统内出现重复,你就会面临“修改其中一个却未能修改另一个”的风险。通常,找出重复也有一定的难度。

        所以,某个函数在各个子类中的函数体都相同(它们很可能是通过复制粘贴得到的),这就需要使用函数上移,也就是将共同的函数上移到超类中

        函数上移过程中,被提升的函数可能会引用只出现于子类而不出现于超类的特性。此时,我就得用字段上移函数上移先将这些特性(类或者函数)提升到超类。 

2、字段上移(Pull Up Field) 

重构改善既有代码的设计-学习(六):处理继承关系_第2张图片 

         如果各子类是分别开发的,或者是在重构过程中组合起来的,你常会发现它们拥有重复特性,特别是字段更容易重复。

        如果它们被使用的方式很相似,就可以将它们提升到超类中去。 

3、构造函数本体上移(Pull Up Constructor Body) 

重构改善既有代码的设计-学习(六):处理继承关系_第3张图片 

        逐一移除子类间的公共代码,将其提升至超类构造函数中。对于公共代码中引用到的变量,将其作为参数传递给超类的构造函数。

        如果重构过程过于复杂,可以考虑转而使用以工厂函数取代构造函数(详见:重构改善既有代码的设计-学习(五):重构API-CSDN博客)。

4、函数下移(Push Down Method)

重构改善既有代码的设计-学习(六):处理继承关系_第4张图片 

        如果超类中的某个函数只与一个(或少数几个)子类有关,那么最好将其从超类中挪走,放到真正关心它的子类中去。 

5、字段下移(Push Down Field) 

重构改善既有代码的设计-学习(六):处理继承关系_第5张图片 

        如果某个字段只被一个子类(或者一小部分子类)用到,就将其搬移到需要该字段的子类中。 

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

重构改善既有代码的设计-学习(六):处理继承关系_第6张图片 

        软件系统经常使用类型码表现“相似但又不同的东西”。大多数时候,有这样的类型码就够了。但也有些时候,可以再多往前一步,引入子类。

        继承有两个诱人之处:

  1. 你可以用多态来处理条件逻辑。如果有几个函数都在根据类型码的取值采取不同的行为,多态就显得特别有用。引入子类之后,我可以用以多态取代条件表达式(详见:重构改善既有代码的设计-学习(四):简化条件逻辑-CSDN博客)来处理这些函数。 
  2. 有些字段或函数只对特定的类型码取值才有意义,此时可以创建子类,然后用字段下移把这样的字段放到合适的子类中去。

        例如:

function createEmployee(name, type) {
    return new Employee(name, type);
}

        改为:

function createEmployee(name, type) {
    switch (type) {
        case "engineer": return new Engineer(name);
        case "salesman": return new Salesman(name);
        case "manager": return new Manager (name);
    }
}

7、移除子类(Remove Subclass) 

重构改善既有代码的设计-学习(六):处理继承关系_第7张图片 

        子类数据结构的多样和行为的多态提供支持,它们是针对差异编程的好工具。但随着软件的演化,子类所支持的变化可能会被搬移到别处,甚至完全去除,这时子类就失去了价值。有时添加子类是为了应对未来的功能,结果构想中的功能压根没被构造出来,或者用了另一种方式构造,使该子类不再被需要了。 

8、提炼超类(Extract Superclass)

重构改善既有代码的设计-学习(六):处理继承关系_第8张图片 

         如果看见两个类在做相似的事,可以利用基本的继承机制把它们的相似之处提炼到超类

9、折叠继承体系(Collapse Hierarchy) 

重构改善既有代码的设计-学习(六):处理继承关系_第9张图片 

         如果发现一个类与其超类已经没多大差别,不值得再作为独立的类存在,就需要把超类和子类合并起来

10、 以委托取代子类(Replace Subclass with Delegate)

重构改善既有代码的设计-学习(六):处理继承关系_第10张图片 

        如果一个对象的行为有明显的类别之分,继承是很自然的表达方式。我可以把共用的数据和行为放在超类中,每个子类根据需要覆写部分特性。

        但继承也有其短板,他只能用于处理一个方向上的变化,并且继承给类之间引入了非常紧密的关系。在超类上做任何修改,都很可能破坏子类。

        用委托可以解决这两个问题。对于不同的变化原因,我可以委托给不同的类。与继承关系相比,使用委托关系时接口更清晰、耦合更少。

        可以用设计模式来理解本重构手法,就是用状态(State)模式或者策略(Strategy)模式
取代子类。这两个模式在结构上是相同的,都是由宿主对象把责任委托给另一个继承体系。

        例子:

class Order {
    get daysToShip() {
        return this._warehouse.daysToShip;
    }
}

class PriorityOrder extends Order {
    get daysToShip() {
        return this._priorityPlan.daysToShip;
    }
}

        改为:

class Order {
    get daysToShip() {
        return (this._priorityDelegate)
            ? this._priorityDelegate.daysToShip
            : this._warehouse.daysToShip;
    }
}

class PriorityOrderDelegate {
    get daysToShip() {
        return this._priorityPlan.daysToShip
    }
}

11、以委托取代超类(Replace Superclass with Delegate) 

重构改善既有代码的设计-学习(六):处理继承关系_第11张图片 

        在对象技术发展早期,有一个经典的误用继承的例子:让栈(stack)继承列表(list)。这个想法的出发点是想复用列表类的数据存储和操作能力。虽说复用是一件好事,但这个继承关系有问题:列表类的所有操作都会出现在栈类的接口上,然而其中大部分操作对一个栈来说并不适用。更好的做法应该是把列表作为栈的字段,把必要的操作委派给列表就行了。 

         如果子类与超类之间的耦合过强,超类的变化很容易破坏子类的功能,此时就可以使用以委托取代超类。

        这样做的缺点就是,对于宿主类(也就是原来的子类)和委托类(也就是原来的超类)中原本一样的函数,现在我必须在宿主类中挨个编写转发函数。

        例子:

class List {...}
class Stack extends List {...}

        改为:

class Stack {
    constructor() {
        this._storage = new List();
    }
}
class List {...}

你可能感兴趣的:(架构,重构,学习)