Skip to content

Latest commit

 

History

History
232 lines (122 loc) · 8.99 KB

第11章 处理概括关系.md

File metadata and controls

232 lines (122 loc) · 8.99 KB

11.1 Pull up Field(字段上移)

将该字段移至超类

做法

  • 针对待提升之字段,检查他们的所有被使用点,确认他们以同样的方式被使用。

  • 如果这些字段的名称不同,将他们改名,使每个名称都和你想为超类字段取的名称相同。

  • 编译,测试。

  • 在超类中新建一个字段。

    如果这些字段是private的,你必须将超类的字段声明为protected,这样子类才能引用他。

  • 移除子类中的字段。

  • 编译测试

  • 考虑对超类的新建字段使用self Encapsulate Field(171).

11.2 PULL up Method(函数上移)

将函数移至超类

做法

  • 检查待提升函数,确定他们是完全一样的。

    如果这些函数看上去做了相同的事,但并不完全一致,可使用Substitute Algorithm(139)让他们变得完全一致。

  • 如果待提升函数的签名不同,将那些签名都改成你想要在超类中使用的签名。

  • 在超类中新建一个函数,将某个待提升函数的代码复制到其中,做适当调整,然后编译。

    如果你使用的是一种强类型的语言,而待提升函数又调用了一个只出现与子类而未出现于超类的函数,你可以在超类中为被调用函数声明一个抽象函数。

    如果待提升函数使用了子类的一个字段,你可以使用pull up field(320)将该字段也提升到超类。或者也可以先使用Self Encapsulate Field(171)然后在超类中把取值函数声明为抽象函数。

  • 移除一个待提升的子类函数。

  • 编译测试。

  • 逐一移除待提升的子类函数,直到只剩下超类中的函数为止。每次移除之后都需要测试。

  • 观察该函数的调用者,看看是否可以改为使用超类类型的对象。

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

在超类中新建一个构造函数,并在子类构造函数中调用它

做法

  • 在超类中定义一个构造函数。

  • 将子类构造函数中的共同代码搬移到超类构造函数中。

  • 被搬移的可能是子类构造函数的全部内容。

    首先设法将共同代码搬移到子类构造函数起始处,然后在复制到超类构造函数中。

  • 将子类构造函数中的共同代码删除,改而调用新建的超类构造函数。

    如果子类构造函数中所有代码都是一样的,那么子类构造函数就只需要调用超类构造函数。

  • 编译测试。

    如果日后子类构造函数再出现共通代码,你可以首先使用Extract Method(110)将那一部分提炼到一个独立函数,然后使用pull up Method(322),将该函数上移到超类。

11.4 Push Down Method(函数下移)

将这个函数移到相关的那些子类中去

做法

  • 在所有子类中声明该函数,将超类中的函数本体复制到每个子类函数中。

    可能需要将超类的某些字段声明为protected,让子类也可以访问。

  • 删除超类中的函数。

    你可能必须修改调用端的某些变量声明或参数声明,以便能使用子类。

    如果有必要通过一个超类对象访问该函数,或不想把该函数从任何子类中移除,再或超类是抽象类,那么就可以在超类中把该函数声明为抽象函数。

  • 编译,测试。

  • 将该函数从所有不需要他的那些子类中删除。

  • 编译测试

11.5 Push Down Field(字段下移)

将这个字段移动到需要他的那些子类中

做法

  • 在所有子类中声明该字段。
  • 将该字段从超类中删除。
  • 编译测试
  • 将该字段从所有不需要他的子类中删除。
  • 编译测试。

11.6 Extract Subclass(提炼子类)

新建一个子类,将上面所说的那一部分特性移到子类中

做法

  • 为源类定义一个新的子类。

  • 为这个新的子类提供构造函数。

    让子类构造函数接受与超类构造函数相同的参数,并通过super调用超类构造函数。

    如果希望对用户隐藏子类的存在,可使用Repalce Constructor with Factory Method(304)

  • 找出调用超类构造函数的所有地点。如果他们需要的是新建的子类,令他们改而调用新构造函数。

    如果子类构造函数需要的参数和超类构造函数的参数不同,可以使用rename Method(273)修改其参数列。如果子类构造函数不需要超类构造函数的某些参数,可使用Rename Method(273)将他们去除。

    如果不再需要直接创建超类的实例,将超类声明为抽象类。

  • 逐一使用Push down method(328)和Push down field(329)将源类的特性移到子类去。

    和Extract class (149)不同的是,先处理函数在处理数据,会简单。

  • 找到所有这样的字段:他们所传达的信息如今可有继承体系自身传达(这一类字段通常是boolean或类型码)。以self Encapsulate Field(171)避免使用这些字段。然后将他们的取值函数替换成多态常量函数。所有使用这些字段的地方都应该以Repalce conditional with Polymorphism(255)重构。

    任何函数如果位于源类之外,又使用了上述字段的访问函数,考虑以Move Method(142)将它移到源类中。然后在使用Repalce conditional with Polymorphism。

  • 每次下移之后,编译测试。

11.7 Extract Superclass(提炼超类)

为这两个类建立一个超类,将相同特性移至到超类

做法

  • 为原本的类新建一个空白的抽象超类。
  • 运用Pull up Field(320),PUll Up Method(322)和Pull up Constructor body(325)逐一将子类的共同元素上移到超类。
  • 每次上移后,编译测试。
  • 检查留在子类中的函数,看他们是否还有共通成分。
  • 将所有共通元素都上移到超类中,检查子类的 所有用户,如果他们只是使用共同接口,你就可以他们请求的对象类型改为超类。

11.8 Extract Interface(提炼接口)

将相同的子集提炼到一个独立接口中

做法

  • 新建一个空接口。
  • 在接口中声明待提炼类的共通操作。
  • 让相关的类实现上述接口。
  • 调整客户端的类型声明,令其使用该接口。

11.9 Collapse Hierarchy(折叠继承体系)

将他们合为一体

做法

  • 选择你想移除的类,是超类还是子类。
  • 使用Pull Up Field(320)和Pull up Method(322),或者Push Down Method(328)和Push Down Field(329),把想要移除的类的所有行为和数据搬移到另一个类。
  • 每次搬移后,编译并测试。
  • 调整即将被移除的那个类的所有引用点,令他们改而引用合并后留下的类。这个动作将会影响变量的声明,参数的类型以及构造函数。
  • 移除我们的目标,此时的它应该已经成为一个空类。
  • 编译测试。

11.10 Form Template Method(塑造模板函数)

将这些操作分别放在独立函数中,并保持他们都有相同的签名,于是原函数也变得相同了,然后将原函数上移至超类。

做法

  • 在各个子类中分解目标函数,使得分解后的各个函数要不完全相同,要不完全不同。

  • 运用Pull Up Method(322)将各子类完全相同的函数上移到超类。

  • 对于那些(剩余的,存在于各子类内的)完全不同的函数,实施Rename Method(273),使所有函数的签名完全相同。

    这将使得原函数变为完全相同,因为他们都执行同样一组函数调用。但子类会以不同方式响应这些调用。

  • 修改上述所有签名后,编译并测试。

  • 运用Pull Up Method(322)将所有原函数逐一上移至超类。在超类中将那些代表各种不同操作的函数定义为抽象函数。

  • 编译测试。

  • 移除其他子类中的原函数,每删除一个,编译并测试。

11.11 Replace Inheritance with Delegation(以委托取代继承)

在子类中新建一个字段用以保存超类,调整子类函数;令他改而委托超类,然后去掉两者之间的继承关系。

做法

  • 在子类中新建一个字段,使其引用超类的一个实例,并将它初始化为this.

  • 修改子类内的所有函数,让他们不再使用超类,转而使用上述那个受托字段,每次修改后,编译并测试。

    你不能这样修改子类中通过super调用超类函数的代码,否则他们会陷入无限递归。这种函数只有在继承关系被打破后才能修改。

  • 去除两个类之间的继承关系,新建一个受托类的对象赋给受托字段。

  • 针对客户端所用的每个超类函数,为他添加一个简单的受托函数,

  • 编译测试。

11.12 Replace Delegation with Inheritance(以继承取代委托)

做法

  • 让委托端成为受托端的一个子类。
  • 编译,
  • 将受托字段设为该字段所处对象本身。
  • 去掉简单的委托函数。
  • 编译测试。
  • 将所有其他设计委托关系的代码,改为调用对象自身,
  • 移除受托字段。