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

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 {...}
相关推荐
老蒋新思维6 小时前
创客匠人峰会实录:知识变现的场景化革命 —— 创始人 IP 如何在垂直领域建立变现壁垒
网络·人工智能·tcp/ip·重构·知识付费·创始人ip·创客匠人
老蒋新思维6 小时前
创客匠人峰会深度解析:智能体驱动知识变现的数字资产化路径 —— 创始人 IP 的长期增长密码
人工智能·网络协议·tcp/ip·重构·知识付费·创始人ip·创客匠人
专注于大数据技术栈7 小时前
java学习--枚举(Enum)
java·学习
我命由我123457 小时前
开发中的英语积累 P19:Inspect、Hint、Feedback、Direction、Compact、Vulnerability
经验分享·笔记·学习·职场和发展·求职招聘·职场发展·学习方法
qq_12498707539 小时前
基于SpringBoot学生学习历史的选课推荐系统的设计与实现(源码+论文+部署+安装)
java·spring boot·后端·学习·毕业设计·毕设
崇山峻岭之间9 小时前
C++ Prime Plus 学习笔记033
c++·笔记·学习
暗然而日章10 小时前
C++基础:Stanford CS106L学习笔记 7 类
c++·笔记·学习
思成不止于此10 小时前
【MySQL 零基础入门】DDL 核心语法全解析:数据库与表结构操作篇
数据库·笔记·学习·mysql
学编程的闹钟10 小时前
86【CSS的模块化处理】
学习
weixin_4093831210 小时前
姜丹四方向a*寻路学习记录4 不用计算起点到目的地的f 从开始计算的是角色起点四周的格子到目的地的f
学习