处理概括关系

0. 本章内容导图

本章所介绍的重构手法专门用来处理类的概括关系(generalization,即继承关系)。


处理概括关系

1. 重构手法

1.1 字段上移

概要:
两个子类拥有相同的字段。
将该字段移至超类。
动机:
a. 处理兄弟类间重复的字段以及因重复字段造成的函数重复
示例图:

字段上移

总结:
    如果子类是分别开发的,或者是在重构过程中组合起来的,往往会拥有重复性,尤其是字段重复。命名并非判断字段是否重复的标准,要依据它们是如何被方法使用的。

1.2 函数上移

概要:
有些函数,在各个子类中产生完全相同的结果。
将该函数移至超类。
动机:
a. 避免行为重复
示例图:

函数上移

总结:
    重复是滋生bug的温床,任何时候,只要系统内出现重复,都应该设法去除重复。相较于“提炼函数”,”函数上移“特指出现在兄弟类之间的函数重复。

1.3 构造函数本体上移

概要:
你在各个子类中拥有一些构造函数,它们的本体几乎完全一致。
在超类中新建一个构造函数,并在子类构造函数中调用它。
动机:
a. 处理对象构建过程中的代码重复
示例:
重构前:

class Manager extends Employee {
    public Manager(String name, String id, int grade) {
        mName = name;
        mId = id;
        mGrade = grade;
    }
}

重构后:

public Manager(String name, String id, int grade) {
    super(name, id);
    mGrade = grade;
}

总结:
    构造函数不是普通的函数,比使用普通函数有更多的限制,因此对于构造函数中涉及的重复要同普通函数中的重复区别开来。

1.4 函数下移

概要:
超类中的某个函数只与部分(而非全部)子类有关。
将这个函数移到相关的那些子类去。
动机:
a. 超类中的存在应该是所有子类的共同抽象,与子类有关的函数应在子类中处理
示例图:

函数下移

总结:
    超类中的字段和接口应该是所有子类共同拥有的特征,不应将对某些子类的特殊处理放在超类中进行。这也是对依赖倒转原则的遵守。

依赖倒转原则:抽象不应该依赖于细节,细节应当依赖于抽象。换句话说就是,要针对接口编程,而非针对实现编程。

1.5 字段下移

概要:
超类中的某个字段只被部分(而非全部)子类用到。
将这个字段移到需要它的那些子类去。
动机:
a. 超类中的存在应该是所有子类的共同抽象,与子类有关的字段应在子类中处理
示例图:

字段下移

总结:
    超类应该是所有子类都具有的共同特征的抽象。

1.6 提炼子类

概要:
类中的某些特性只被某些(而非全部)实例用到。
新建一个子类,将上面所说的那一部分特性移到子类中。
动机:
a. 类中的某些行为只被一部分实例用到,其他实例不需要它们
示例图:

提炼子类

总结:
    类是对象的蓝图,类定义了使用该类创建的所有对象具有的属性和行为。如果类中的某些特性不是全部实例都具备的,就应该将那些部分实例才具有的特性泛化到子类中。

泛化:一般事物(称为超类或父类)和该事物的较为特殊的种类(称为子类)之间的关系。

1.7 提炼超类

概要:
两个类有相似特性。
为这两个类建立一个超类,将相同特性移至超类。
动机:
a. 消除系统中两个相似类中的重复,建立抽象体系
示例图:

提炼超类

总结:
    有很多因素会导致系统中出现共通性的类,没有为它们建立出继承结构,此重构手法就是针对这种情况,帮助找出共通性,建立继承结构。这是一种抽象的过程。

抽象关注一个对象的外部视图,可以用来分离对象的基本行为和它的实现。

1.8 提炼接口

概要:
若干客户使用类接口中的同一子集,或者两个类的接口有部分相同。
将相同的子集提炼到一个独立接口中。
动机:
a. 针对接口编程
b. 接口隔离,分离责任
示例图:

提炼接口

总结:
    针对客户的使用分离责任可以使系统的用法更加清晰,同时也容易看清系统的责任划分。你可以从同一个类建立多个不同的接口,以满足多个客户不同的需要。这也是接口隔离原则要求的实现方式。

接口隔离原则:使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。

1.9 折叠继承体系

概要:
超类和子类之间无太大区别。
将它们合为一体。
动机:
a. 消除不必要的继承关系
示例图:

折叠继承体系

总结:
    系统中的每个类都是需要维护的,当发现系统中有这种设计或者由于其他重构出现了这种情况,就需要果断处理。

1.10 塑造模板函数

概要:
你有一些子类,其中相应的某些函数以相同顺序执行类似的操作,但各个操作的细节上有所不同
将这些操作分别放进独立函数中,并保持它们都有相同的签名,于是原函数也就变得相同了。然后将原函数上移至超类。
动机:
a. 消除重复,分离不变性和可变性
示例图:

塑造模板函数

总结:
    这是通过重构实现模板方法模式的过程。

模板方法模式:定义一个操作中算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

1.11 以委托取代继承

概要:
某个子类只使用超类接口中的一部分,或是根本不需要继承而来的数据。
在子类中新建一个字段用以保存超类;调整子类函数,令它改而委托超类;然后去掉两者之间的继承关系。
动机:
a. 消除误用的泛化关系
示例图:

以委托取代继承

总结:
    对于继承关系,你需要理解父类和子类之间应该是一种is-a的关系,如果仅仅是因为代码复用而采用继承是大错特错的。继承和组合都是实现代码复用的方式,使用它们之前,要先弄明白类之间的关系。

1.12 以继承取代委托

概要:
你在两个类之间使用委托关系,并经常为整个接口编写许多极简单的委托函数。
让委托类继承受托类。
动机:
a. 需要使用受托类中的所有函数,且所有委托函数都是极简单的
示例图:

以继承取代委托

总结:
    要注意此重构手法使用的条件,即委托类为受托类的所有接口都编写了极简单的委托函数。合成复用原则告诉我们,在涉及代码复用时,要优先考虑组合,而非继承。但当对象之间是一种泛化的关系时,就要考虑将依赖关系改为泛化关系。
    如果受托对象被不止一个其他对象共享,而且受托对象是可变的,就不能将委托关系替换为继承关系,因为不能影响到其他对象对该受托对象的使用及数据共享。

合成复用原则:尽量使用对象组合,而不是继承来达到复用的目的。即复用时要优先考虑组合,而非继承。

你可能感兴趣的:(处理概括关系)