Scala 与设计模式(六):Bridge 桥接模式
Bridge 桥接模式是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。
相信大家都玩过「俄罗斯方块」吧。
小罗年幼时最喜欢玩的就是俄罗斯方块。作为一个有情怀的程序员,小罗决定尝试实现这款游戏。
玩过俄罗斯方块的人都会知道,俄罗斯方块由七种简单形状组成:
- I、J、L、O、S、T、Z
小罗了然于心,抄起手中的键盘就创建了七个类。
黑色过于单调,所以小罗又选了三种颜色准备为这些方块着色:
- Yellow
- Blue
- Red
要实现这样的需求,最 low 的方法就是为每种形状创造所有颜色的版本。
如果采用这种方案,双方之间处于强链接,类之间关联性极强,如要进行扩展,必然导致类结构急剧膨胀:
如果仅用继承实现,我们会创造至少 3 * 7 = 21 个类。
当我们想增加 1 种形状(或颜色)的时候,就需要新增 3 (或 7)个类。
数量爆炸的类 == 差劲的扩展能力 == 爆炸的维护成本!
那有没有环保一点的方式呢?让我们来看看「桥接模式」是怎么解决的。
概念
桥接模式的定义比较简洁:
把事物对象和其具体行为、具体特征分离开来,使它们可以各自独立的变化。 —— wikipedia
换言之,即 抽象化与实现化解耦,使得二者可以独立变化。
根据 GOF 提到的,桥接模式由四部分组成:
- 抽象类:定义了一个实现类接口类型的对象并可以维护该对象。
- 扩充抽象类:扩充由抽象类定义的接口,它实现了在抽象类中定义的抽象业务方法,在扩充抽象类中可以调用在实现类接口中定义的业务方法。
- 实现类接口:定义了实现类的接口,实现类接口仅提供基本操作,而抽象类定义的接口可能会做更多更复杂的操作。
- 具体实现类:实现了实现类接口并且具体实现它,在不同的具体实现类中提供基本操作的不同实现,在程序运行时,具体实现类对象将替换其父类对象,提供给客户端具体的业务操作方法。
Java 实现
在使用桥接模式时,我们首先应该识别出一个类所具有的两个独立变化的维度,将它们设计为两个独立的继承等级结构,为两个维度都提供抽象层,并建立抽象耦合。
即,我们需要根据实际需求对形状和颜色进行组合。
既然是组合,接口肯定是少不了的,先创建颜色接口(这里也称作「桥接口」):
以及各种颜色类:
然后,我们创建最重要的形状抽象类:
同样创建具体的方块:
测试:
以上,我们将「形状」和「颜色」解耦。
Hint: 如果你依旧有所疑惑,请回顾最开始的定义:
把事物对象和其具体行为、具体特征分离开来,使它们可以各自独立的变化。
此时,如需添加新的颜色或形状,我们只用实现一个桥接口或者继承一个抽象类即可。
优缺点
以上,相信你对桥接模式已经有所了解。
再我们来看看它的优缺点。
优点
- 抽象和实现的分离。
- 优秀的扩展能力。
- 实现细节对客户透明。
缺点
桥接模式需要建立在你对系统充分的认知下,需要我们识别出两个合理的变化维度,所以适用范围受到限制。
所以你什么时候该使用桥接模式呢?
适用场景
正如我们上方的例子,如果一个场景存在两个独立变化的维度,且这两个维度需要频繁扩展或变动时,我们优先考虑桥接模式。
如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
其他。
Scala 实现
在 Scala 中,桥接模式的实现与 Java 大同小异,我们只需将接口关键字改为 trait
。
颜色接口:
颜色类:
形状抽象类以及实现类:
也许部分同学会问:这里抽象类可以用 trait
代替吗?trait
扩展性会不会更好?具体还是参考这里吧:abstract class 比 trait 好在哪里?
测试:
总结
桥接模式用一种巧妙的方式处理多层继承存在的问题,用抽象关联取代了传统的多层继承,将类之间的静态继承关系转换为动态的对象组合关系,使得系统更加灵活,并易于扩展,同时有效控制了系统中类的个数。在系统设计初期,合理利用桥接模式,会让系统更加优雅。
源码链接
如有错误和讲述不恰当的地方还请指出,不胜感激!