大话设计模式二十之桥接模式

大鸟玩儿的游戏“魂斗罗”可以安装到菜鸟的手机上吗?

菜鸟的手机是M品牌的,大鸟的手机是N品牌的,按道理大鸟玩儿的游戏菜鸟是不能玩儿的。

手机为什么不能统一一下软件呢?

在手机真正发展的十年之间,各大手机厂商都发展自己的软件部门开发手机软件,哪怕是同一品牌的手机,不同型号的也完全又可能软件不兼容。

但是在计算机领域里,就完全不一样了。比如有Windows操作系统,使得所有的PC厂商不用关注软件,而软件制造商也不用过多关注硬件,这对计算机的整体发展是非常有利的。而又个别品牌的电脑公司自己开发操作系统和应用软件,尽管充满了创意,但却因为不能与其他软件整合,而使得发展缓慢。

手机也可以学计算机,由专业公司开发操作系统和应用软件,手机商只要好好把手机硬件做好就行了。

统一谈何容易?谁做的才算是标准呢?而谁又不希望自己的硬件和软件成为标准,然后一统天下?这里又很多商业竞争的问题,不过目前很多只能手机都在朝这个方向发展。


一、紧耦合的程序演化

手机硬件软件和PC硬件软件蕴含两种完全不同的思维方式。


如果现在有一个N品牌的手机,它有一个小游戏,我要玩儿游戏,程序应该如下:



现在又有一个M品牌的手机,也有小游戏,客户端也可以调用,如何做?

两个品牌,都有游戏,从面向对象的思想来说,应该有一个父类‘手机品牌游戏’,然后让N和M品牌的手机游戏都继承于它,这样可以实现统一的运行方法。这就是抽象的感觉。



然后,由于手机都需要通讯录功能,于是N品牌和M品牌都增加了通讯录的增删改查功能。如何处理?

这就意味着,父类应该是‘手机品牌’,下有‘手机品牌M’和‘手机品牌N’,每个子类下各有‘通讯录’和‘游戏’子类





这个结构应该还是可以的,如果现在需要每个品牌都增加一个MP3音乐播放功能,如何做?

就在每个品牌的下面都增加一个子类?

这两个子类的差别大不大?

应该是不大的,不过没有办法,因为品牌不同,增加功能就必须要这样的。

现在又来了一家新的手机品牌‘S’,它也又游戏、通讯录、MP3音乐播放功能,如何处理?

那就得再增加‘手机品牌S’类和三个下属功能子类。

这就很麻烦了,如果还需要增加‘输入法’功能、‘拍照’功能,再增加‘L品牌’、‘X品牌’类如何写?


换一种方式如何呢?

结构图:


这样问题也得不到解决。要是增加手机功能或是增加品牌都会产生很大的影响。

问题出在哪里呢?

一直在用面向对象的理论设计的,先有一个品牌,然后多个品牌就抽象出一个品牌抽象类,对于每个功能,就都继承各自的品牌,或者,不从品牌,从手机软件的角度去分类,这有什么问题呢?

是呀,就像刚开始学会用面向对象的继承时,感觉既新颖又功能强大,所以只要可以用,就都用上继承。这就好比是‘有了新锤子,所有的东西看上去都成了钉子’。但事实上,很多情况用继承会带来麻烦。

比如,对象的继承关系是在编译时就定义好了,所以无法在运行时改变从父类继承的实现。子类的实现与它的父类有非常紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化。当你需要复用子类时,如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。


在面向对象设计中,我们还有一个很重要的设计原则,那就是合成/聚合复用原则。即优先使用对象合成/聚合,而不是类继承。


二、合成/聚合复用原则

合成/聚合复用原则(CARP),尽量使用合成/聚合,尽量不要使用类继承。

合成(Composition,也有翻译成组合)和聚合(Aggregation)都是关联的特殊种类。

聚合表示一种若的‘拥有’关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分;合成则是一种强的‘拥有’关系,体现了严格的部分和整体的关系,部分和整体的生命周期一样。


比如,大雁有两个翅膀,翅膀与大雁是部分和整体的关系,并且它们的生命周期是相同的,于是大雁和翅膀就是合成关系。而大雁是群居动物,所以每只大雁都是属于一个雁群,一个雁群可以有多只大雁,所以大雁和雁群是聚合关系。



合成/聚合复用原则的好处是,优先使用对象的合成/聚合将有助于你保持每个类并封装,并被集中在单个任务上。这样类和类继承层次会保持较小规模,并且不太可能增长为不可控制的庞然大物。


上面的问题,需要学会用对象的职责,而不是结构来考虑问题。其实答案就在之前聊到的手机与PC电脑的差别。

手机是不同的品牌公司,各自做各自的软件,就像现在的设计一样,而PC却是硬件厂商做硬件,软件厂商做软件,组合起来才是可以用的机器。

实际上,像‘游戏’、‘通讯录’、‘MP3音乐播放’这些功能都是软件,如果我们可以让其分离与手机的耦合,那么就可以大大减少面对新需求时改动过大的不合理情况。

意思就是应该有个‘手机品牌’抽象类和‘手机软件’抽象类,让不同的品牌和功能都分别继承于它们,这样要增加新的品牌或新的功能就不用影响其它类了。



还剩个问题,手机品牌和手机软件之间的关系呢?

应该是手机品牌包含有手机软件,但软件并不是品牌的一部分,所以它们之间是聚合关系。



三、松耦合的程序




现在感觉好很多,如果要增加一个功能,比如MP3音乐播放功能,那么只要增加这个类就性了。不会影响其他任何类。类的个数增加也只是一个。


如果要增加S品牌,只需要增加一个品牌子类就可以了。个数也是一个,不会影响其他类的改动。


这显然符合开放-封闭原则,这样的设计显然不会修改原来的代码,而只是扩展类就行了。但今天我的感受最深的是合成/聚合复用原则,也就是优先使用对象的合成或聚合,而不是类继承。聚合的魅力无限呀。相比,继承的确很容易造成不必要的麻烦。

盲目使用继承当然会造成麻烦,而其本质原因是什么?

继承是一种强耦合的结构,父类便,子类就必须要便。

所以在使用继承时,一定要在是‘is-a’的关系时再考虑使用,而不是任何时候都去使用。



四、桥接模式

桥接模式(Bridge),将抽象部分与它 的实现部分分离,使它们都可以独立地变化。

抽象与它的实现分离,这并不是说,让抽象类与其派生类分离,因为这没有任何意义。实现指的是抽象类和它的派生类用来实现自己的对象。


就刚才的例子而言,就是让‘手机’既可以按照品牌来分类,也可以按照功能来分类。


由于实现的方式有多种,桥接模式的核心意图就是把这些实现独立出来,让它们各自地变化。这就使得每种实现的变化不会影响其他实现,从而达到应对变化的目的。


五、桥接模式基本代码



桥接模式就是将抽象部分与它的实现部分分离,其实就是实现系统可能有多角度分类,每一种分类都可能变化,那么就把这种多角度分离出来让它们独立变化,减少它们之间的耦合。


在发现我们需要多角度去分类实现对象,而只用继承会造成大量的类增加,不能满足开放-封闭原则时,就应该要考虑用桥接模式了。


只要真正深入地理解了设计模式,很多设计模式其实就是原则的应用而已,或许在不知不觉中就在使用设计模式了。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值