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

1、函数上移(Pull Up Method)

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

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

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

2、字段上移(Pull Up Field)

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

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

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

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

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

4、函数下移(Push Down Method)

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

5、字段下移(Push Down Field)

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

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

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

继承有两个诱人之处:

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

例如:

javascript 复制代码
function createEmployee(name, type) {
    return new Employee(name, type);
}

改为:

javascript 复制代码
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)

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

8、提炼超类(Extract Superclass)

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

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

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

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

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

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

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

可以用设计模式来理解本重构手法,就是用状态(State)模式或者策略(Strategy)模式

取代子类。这两个模式在结构上是相同的,都是由宿主对象把责任委托给另一个继承体系。

例子:

javascript 复制代码
class Order {
    get daysToShip() {
        return this._warehouse.daysToShip;
    }
}

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

改为:

javascript 复制代码
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)

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

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

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

例子:

javascript 复制代码
class List {...}
class Stack extends List {...}

改为:

javascript 复制代码
class Stack {
    constructor() {
        this._storage = new List();
    }
}
class List {...}
相关推荐
西岸行者13 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意13 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码13 天前
嵌入式学习路线
学习
GEO行业研究员13 天前
AI是否正在重构个体在健康相关场景中的决策路径——基于系统建模与决策链条结构分析的讨论
人工智能·算法·重构·geo优化·医疗geo·医疗geo优化
毛小茛13 天前
计算机系统概论——校验码
学习
babe小鑫13 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms13 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下13 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。13 天前
2026.2.25监控学习
学习
im_AMBER13 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode