跳转到内容

折叠继承体系

折叠继承体系(Collapse Hierarchy)把已经没有区别的父类和子类合并成一个类。 它解决的痛是:读代码的人以为子类有什么特别之处,点进去却发现它是空的,每次都白跳一趟,新人还要猜该用哪一个。

小许的系统里有 Employee 和子类 Salesman。当初销售有提成和指标,后来公司改成人人都有绩效奖金,这些成员一个个上移到了 Employee(第 51 章),Salesman 里最后什么都不剩了,可创建销售时代码还在写 Salesman(...)。

"""折叠继承体系:重构前。"""
class Employee:
def __init__(self, name: str) -> None:
self.name = name
def badge(self) -> str:
return f"员工 {self.name}"
class Salesman(Employee):
pass
print(Salesman("李娜").badge())
# 输出:
# 员工 李娜
// 折叠继承体系:重构前。
class Employee {
constructor(readonly name: string) {}
badge(): string {
return `员工 ${this.name}`;
}
}
class Salesman extends Employee {}
console.log(new Salesman("李娜").badge());
// 输出:
// 员工 李娜
"""折叠继承体系:重构后。"""
class Employee:
def __init__(self, name: str) -> None:
self.name = name
def badge(self) -> str:
return f"员工 {self.name}"
print(Employee("李娜").badge())
# 输出:
# 员工 李娜
// 折叠继承体系:重构后。
class Employee {
constructor(readonly name: string) {}
badge(): string {
return `员工 ${this.name}`;
}
}
console.log(new Employee("李娜").badge());
// 输出:
// 员工 李娜

Salesman 删掉了,创建它的地方改成创建 Employee,输出不变。少了一个空类,读代码的人不必再去确认它有没有覆盖什么;类型检查里出现的 Salesman 也一并消失,不会再有人纠结函数参数该写 Employee 还是 Salesman。

做法很简单:先决定留下哪一个(通常留名字更贴切的那个,这里留 Employee),把另一个里剩下的字段和方法用上移或下移(第 51 章)搬过来,再把所有引用改成留下的类,最后删掉空类。IDE 的 Find Usages 和 Safe Delete 能帮着找全引用(IDE 可自动完成)。

折叠继承体系:父类和子类差别已经不值得分开时,把它们合并成一个。

它常是别的重构做完之后的收尾:成员一个个上移,子类空了;或者成员一个个下移,父类只剩一个子类在用,也可以把父类并进子类。另一个来源是夸夸其谈通用性:当初为“以后可能会有别的种类”预留了一层继承,以后一直没来。

子类虽然空,但外部代码在按类型区分它时别急着合并:数据库里存着类型列、序列化格式里写着类名、日志按类名统计,删掉子类就会改变这些外部可见的行为,要先确认它们都不依赖类名。如果子类空着但下一个需求马上就要往里加东西,也不必今天删、明天建。还有一种情况是空的子类在当“标记”用,比如异常层次里一个没有任何新成员的 NotFoundError,调用方靠它精确捕获,这样的空类是在干活的。