为什么有“组合优于继承”的说法

首先需要注意, 广为流传的“组合优于继承” 的说法是一种不严谨的翻译, 其来源如下

众多设计模式包含的2个最核心原则(引自参考书籍 《Design Patterns: Elements of Reusable Object-Oriented Software》)

Program to an interface, not an implementation. (面向接口编程,而不是具体的实现)

Favor object composition over class inheritance.(如果某个场景的代码复用既可以通过类继承实现, 也可以通过对象组合实现, 尽量选择对象组合的设计方式)

第一个原则的好处非常明显: 可以极大程度地减少子系统具体实现之间的相互依赖。

第二个原则则不那么容易理解, 下面展开叙述 。

对象组合与类继承的对比

面向对象设计的过程中, 两个最常用的技巧就是类继承对象组合,同一个场景下的代码复用,这两个技巧基本上都可以完成。 但是他们有如下的区别:

通过继承实现的代码复用常常是一种“白盒复用”, 这里的白盒指的是可见性: 对于继承来说,父类的内部实现对于子类来说是不透明的(实现一个子类时, 你需要了解父类的实现细节, 以此决定是否需要重写某个方法)

对象组合实现的代码复用则是一种“黑盒复用”“: 对象的内部细节不可见,对象仅仅是以“黑盒”的方式出现(可以通过改变对象引用来改变其行为方式)

这里通过汽车的刹车逻辑进行说明。 对于汽车来说, 存在多种不同的型号, 我们会很自然的希望定义一个类 Car 来描述所有汽车通用的刹车行为 brake(), 然后通过某种方式(继承/组合)来为不同的型号的汽车提供不同的刹车行为。

如果通过继承来实现, 思路就是定义一个Car, 实现不同子类 CarModelA, CarModelB 来重写父类的 brake() 方法以体现不同型号车的刹车行为区别。

public abstract class Car {    // 也可以将该方法设置成抽象方法, 强迫子类来实现该方法    public void brake() {      // 提供一个默认的刹车实现      ...    }}public class CarModelA extends Car {    public void brake() {      aStyleBrake();// A 风格的刹车行为    }}public class CarModelB extends Car {    public void brake() {      bStyleBrake(); // B 风格的刹车行为    }}

上述的例子展现了如何通过继承来完成不同型号车辆刹车行为的变化。但是可以注意到, 每一个型号的车的刹车行为是在编译时就确定好的 , 没有办法在运行时刻将 CarModelB 的刹车行为赋予 CarModelA 。

如果通过对象组合的实现方式, 则需要为 Car 定义一个引用, 该引用的类型是一个为刹车行为定义的接口。

public interface IBrakeBehavior {    public void brake();}public class AStyleBrake implements IBrakeBehavior {    public void brake() {        aStyleBrake(); // A 风格的刹车行为    }}public class BStyleBrake implements IBrakeBehavior {    public void brake() {        bStyleBrake(); // B 风格的刹车行为    }}//通过给下面的类赋予 AStyleBrake 或 BStyleBrake 可以完成不同 Model 的刹车行为的切换 // 同理, 汽车其他的行为(如启动 launch) 也可以用类似的方法实现// 不同型号的汽车实现, 可以通过赋予不同风格的行为实例来 “组装” 出来的, 也就不需要为 Car 定义不同的子类了 public class Car{    protected IBrakeBehavior brakeBehavior;    public void brake() {        brakeBehavior.brake();    }    public void setBrakeBehavior(final IBrakeBehavior brakeType) {        this.brakeBehavior = brakeType;    }}

值得注意的是, 上面的刹车行为不一定需要通过接口来实现, 定义一个 BrakeBehaviour 的父类, 然后再定义AStyleBrake , BStyleBrake 来继承该类, 实现不同的行为, 同样是组合方式的应用。

所以不难发现, 当我们拿类继承组合在一起进行对比时, 并不是以实现方式中是否有用到类继承而区分的。

我们真正关注的是行为的继承行为的组合:需要变化的行为是通过继承后重写的方式实现, 还是通过赋予不同的行为实例实现。

继承与组合的优缺点对比

类继承优点:

类之间的继承关系时在编译时刻静态地定义好的, 因此使用起来也非常直观, 毕竟继承是被编程语言本身所支持的功能。

类继承也使得修改要重用的代码变得相对容易, 因为可以仅仅重写要更改的父类方法。

类继承缺点:

第一个缺点是伴随第一个优点而生的: 没有办法在运行时刻改变继承了父类的子类行为。

这一点在之前汽车的例子中已经进行了说明

第二个缺点与第一个缺点相比往往更严重: 通过继承实现的代码复用,本质上把父类的内部实现细节暴露给了子类, 子类的实现会和父类的实现紧密的绑定在一起, 结果是父类实现的改动,会导致子类也必须得改变。

以之前的例子进行说明, 如果是通过继承的方式来实现不同型号汽车的刹车行为变化, 假设现在我们基于 Car 这个父类实现了 10 种不同型号的汽车 CarModel( A, B, C, D, E, F, G,H ,I , J ), 其中前 5 个型号( A、B、C、D、E) 都没有重写父类的刹车方法, 直接使用了父类 Car 提供的默认方法, 后 5 个型号均提供了自己独特的 brake 实现 。 现假设, 我们希望对 Car 中的 brake 方法进行升级改造, 然而,升级改造后的 brake 行为只适用于C,D , 最早的两种型号A, B 并不兼容升级后的刹车行为。 这样, 我们为了保证 A, B 依旧能正常工作, 就不得不把旧的 brake 实现挪到 A、B 中。 或者, 分别去升级 C、 D、E 中的 brake 方法。

对象组合优点:

对象的组合是在运行时刻通过对象之间获取引用关系定义的,所以对象组合要求不同的对象遵从对方所实现的接口来实现引用传递, 这样反过来会要求更加用心设计的接口,以此支持你在使用一个对象时, 可以把它和很多其他的对象组合在一起使用而不会出现问题。

对象的组合由于是通过接口实现的, 这样在复用的过程中是不会打破其封装的。 任意一个对象都可以在运行时刻被替换成另外一个实现了相同接口且类型相同对象, 更重要的是,由于一个对象的实现是针对接口而编写的, 具体实现之间的依赖会更少。

对象组合的方式可以帮助你保持每个类的内聚性,让每个类专注实现一个任务。 类的层次会保持的很小,不会增长到一种无法管理的恐怖数量。 (这也是为什么Java语言支持单继承的原因

对象组合缺点:

不具备之前所罗列的类继承的优点

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容