三寻猿
发布于 2025-07-14 / 5 阅读 / 0 评论 / 0 点赞

理解“优先使用对象组合,而不是类继承”

最近在维护一个老系统的时候,对复杂继承关系的难于维护深有同感,就像《代码大全(第二版)》中说的”大多数人在脑中同时应付超过2到3层继承时就有麻烦了“。应该怎么解决呢?

在GoF的《设计模式:可复用面向对象软件的基础》一书中,有这样一个原则:“优先使用对象组合,而不是类继承”。怎么理解呢?假设我们有三个基础功能“基础功能1”、“基础功能2”、“基础功能3”,三个扩展功能“扩展功能1”、‘扩展功能2“、‘扩展功能3“,扩展功能是基于基础功能扩展而来的,我们可以比较下继承和组合的差别:

耦合程度

开发影响

功能关系

维护成本

OO原则

扩展功能继承基础功能

父类实现细节对子类可见,破坏封装性

  • 编写子类需要了解父类实现

  • 修改父类影响所有子类

功能之间的关系编译时固化,类的上限是:基础功能类3个,扩展功能类9个(9=各个扩展功能和基础功能的笛卡尔积3*3)

继承的层数越多、类越多,阅读就越困难。且随着功能增加呈指数级增长。

-

扩展功能组合基础功能

被组合类完全独立,组合类仅依赖被组合类的方法声明,实现细节不可见

仅依赖被组合类的方法声明,开发相互独立不影响

功能之间的关系运行时组合,类的上限是:基础功能类3个,扩展功能类3个

0继承,且类的数量很有限,阅读容易

满足高内聚低耦合原则

看到这里,会觉得继承怎么啥都不是?显然不是,我认为这两者是配合使用的,对于复杂的功能关系,适合用组合。这个和DDD的领域模型设计是相通的,抽象实体,然后用聚合根组合实体。而对于简单且稳定的原子功能,可以用继承做扩展。也就是说整体上功能和功能之间是组合关系,单个原子功能内在简单、稳定的前提下可以用继承。这里还要考虑到系统在演进的过程中复杂度的变化,及时调整变复杂的继承关系为组合关系。

最后给自己提个醒,Rust、Go比较激进,在语言级去掉了继承,只能使用组合模式。Java比较传统,需要我们在开发中多注意,一定要写可维护的代码。

参考文献:

1、代码大全(第二版)【章节6.3】: ”大多数人在脑中同时应付超过2到3层继承时就有麻烦了“

2、设计模式:可复用面向对象软件的基础【章节1.6.5 】:“优先使用对象组合,而不是类继承”


评论