【设计模式】桥接模式
系列文章目录
文章目录
- 系列文章目录
- 紧耦合的程序演化
- 合成/聚合复用原则
- 松耦合的程序
- 桥接模式
- 桥接模式基本代码
- 总结
2007年苹果手机尚未出世,手机操作系统多种多样(黑莓、塞班、Tizen等),互相封闭,而如今手机操作系统只剩下苹果OS和安卓,鸿蒙正在稳步进场,本章内容也将在2007年那个特定背景故事下展开。
我们知道在计算机领域里,有了Windows操作系统,使得所有的PC厂商不用关注软件,而软件制造商也不用过多关注硬件,这对计算机的整体发展是非常有利的,而有个别品牌的电脑公司自己开发操作系统和应用软件,尽管充满了创意,但是却因为不能和其他软件整合,导致发展缓慢。手机也是这种情况,操作系统多种多样,软件并不能完全整合。
紧耦合的程序演化
但其实这里蕴含两种完全不同的思维方式,分别是手机硬件软件和PC硬件软件
如果现在有一个N品牌的手机,它有一个游戏,我要玩游戏程序应该怎么写
//手机品牌N的游戏class HandsetBrandGame{public void run(){System.out.println("运行N品牌手机游戏");}}
客户端代码:
public static void main(String[] args) {HandsetBrandGame game = new HandsetBrandGame();game.run();}
如果现在又有一个M品牌的手机,也有小游戏,客户端也可以调用。这里是两个品牌都有游戏,从面向对象的思想来说,应该有一个父类‘手机品牌游戏’,然后让N和M品牌的手机游戏都继承于它,这样可以实现同样的运行方法。
//手机游戏类class HandsetGame{public void run(){}}
M品牌游戏和N品牌游戏
//手机品牌M的游戏class HandsetBrandMGame extends HandsetGame{public void run(){System.out.println("运行M品牌手机游戏");}}//手机品牌N的游戏class HandsetBrandNGame extends HandsetGame{public void run(){System.out.println("运行N品牌的手机游戏");}}
另外,由于手机都需要通讯录功能,于是N品牌和M品牌都增加了通讯录的增删改查功能,这样的话就有些麻烦了,那就意味着,父类应该是手机品牌,下有‘手机品牌M’和‘手机品牌N’,每个子类下各有‘通讯录’和‘游戏子类’。
class HandsetBrand{public void run(){}}//手机品牌Mclass HandsetBrandM extends HandsetBrand{}//手机品牌Nclass HandsetBrandN extends HandsetBrand{}//下属的各自通讯录类和游戏类class HandsetBrandMGame extends HandsetBrandM{public void run(){System.out.println("运行M品牌的游戏");}}class HandsetBrandNGame extends HandsetBrandN{public void run(){System.out.println("运行N品牌的游戏");}}class HandsetBrandMAddressList extends HandsetBrandM{public void run(){System.out.println("运行M品牌手机通讯录");}}class HandsetBrandNAddressList extends HandsetBrandN{public void rum(){System.out.println("运行N品牌手机通讯录");}}//客户端public static void main(String[] args) {HandsetBrand ab;ab =new HandsetBrandMAddressList();ab.run();ab = new HandsetBrandMGame();ab.run();ab = new HandsetBrandNAddressList();ab.run();ab = new HandsetBrandNGame();ab.run();}
这个结构是可以的,如果现在需要每个品牌都增加一个音乐播放器功能,只需要在每个品牌的下面都增加一个子类,这几个子类差别是不大,代码类似,但是因为品牌不同,增加功能只能这样。现在又来了一家新的手机品牌‘S’,他也有游戏、通讯录、音乐播放器,我们照上述代码功能,就只能增加手机品牌S类和其三个下属功能子类。这样似乎越来越麻烦了。
我们似乎是一直在用面向对象的理论设计的,先有品牌,然后多个品牌就抽象出一个品牌抽象类,对于每个功能,就都继承其各自的品牌。但是还是造成越来越麻烦的地步,这是因为在某些情况下,使用继承会带来麻烦。比如,对象的继承关系实在编译时就定义好了,所以无法在运行时改变从父类继承的实现。子类的实现与它的父类有非常紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化。当你需要复用子类时,如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。
这样的继承结构,如果不断增加品牌或新功能,类会越来越多的
在面向对象设计中,我们还有一个很重要的设计原则,那就是–合成/聚合复用原则 ,即优先使用对象合成/聚合,而不是类继承。
合成/聚合复用原则
合成/聚合复用原则,尽量使用合成/聚合,尽量不要使用类继承
合成和聚合都是关联的特殊种类。聚合表示一种弱的“拥有”关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分;合成则是一种强的“拥有”关系,体现了严格的部分和整体的关系部分和整体的生命周期一样。比方说,大雁有两个翅膀,翅膀和大雁就是部分和整体的关系,并且它们的生命周期是一样的,于是大雁和翅膀就是合成关系。而大雁是群居动物,所以每只大雁都是属于一个雁群,一个雁群可以有多只大雁,所以大雁和雁群是聚合关系。
合成/聚合复用原则的好处是,优先使用对象的合成/聚成将有助于你保持每个类被封装,并被集中在单个任务上。这样类和类继承层次会保持较小规模,并且不太可能增长为不可控制的庞然大物。 就刚才的例子,我们要学会用对象的职责,而不是结构来考虑问题。其实答案就在我们聊到的手机和电脑的差别上。
手机是不同的品牌公司,各自做自己的软件,就像我们现在的设计一样,而PC却是硬件厂商做硬件,软件厂商做软件,组合起来才可以用的机器。
实际上,像游戏、通讯录、音乐播放这些功能都是软件,如果我们可以让其分离与手机的耦合,那么就可以大大减少面对新需求时改动多大的不合理情况。所以就是该有个手机品牌抽象类和手机软件抽象类,让不同的品牌和功能都分别继承于他们,这样要增加新的品牌或新的功能就不用影响其他类了。 手机品牌和手机软件之间关系是什么呢?我们知道,手机品牌包含手机软件,但是软件并不是品牌的一部分,所以他们之间是聚合关系。
松耦合的程序
手机软件抽象类:
//手机软件abstract class HandsetSoft{//运行public abstract void run();}
游戏、通讯录等具体类:
//手机游戏class HandsetGame extends HandsetSoft{@Overridepublic void run() {System.out.println("手机游戏");}}//手机通讯录class HandsetAddressList extends HandsetSoft{@Overridepublic void run() {System.out.println("通讯录");}}
手机品牌类:
abstract class HandsetBrand{protected HandsetSoft soft;//设置手机软件public void setHandsetSoft(HandsetSoft soft){this.soft = soft;}//运行public abstract void run();}
品牌M和品牌N具体类:
//手机品牌Mclass HandsetBrandM extends HandsetBrand{@Overridepublic void run() {System.out.println("品牌M");soft.run();}}//手机品牌Nclass HandsetBrandN extends HandsetBrand{@Overridepublic void run() {System.out.println("手机品牌N");soft.run();}}
客户端代码:
public static void main(String[] args) {HandsetBrand ab;ab = new HandsetBrandM();ab.run();ab.setHandsetSoft(new HandsetGame());ab.run();ab.setHandsetSoft(new HandsetAddressList());ab.run();HandsetBrand ab2;ab2 = new HandsetBrandN();ab2.setHandsetSoft(new HandsetGame());ab2.run();ab2.setHandsetSoft(new HandsetAddressList());ab2.run();}
这样的代码感觉就会好很多,要是需要增加一个功能,比如音乐播放功能,那么只需要增加这个类就可以了。不会影响其他任何类。类的个数增加也只是一个
class HandsetMusicPlay extends HandsetSoft{@Overridepublic void run() {System.out.println("音乐播放");}}
如果要是增加S品牌,只需要增加一个品牌子类就可以了。个数也是一个,不会影响其他类的改动
class HandsetBrandS extends HandsetBrand{@Overridepublic void run() {System.out.println("品牌S");soft.run();}}
这也符合我们面向对象设计中的开放-封闭原则,但是代码主要还是凸显的合成/聚合复用原则,也就是优先使用对象的合成或聚合,而不是类继承。 盲目的继承会带来麻烦,主要是因为继承是一种强耦合结构,父类变,子类也就必须变, 所以在用继承时,一定要在是“is-a”的关系时再考虑使用,而不是任何时候再去使用。这也就是我们的设计模式–桥接模式。
桥接模式
桥接模式,将抽象部分与它的实现部分分离,使他们都可以独立地变化。
这里我们需要理解下,什么叫抽象与它的实现分离,这并不是说,让抽象类与其派生类分离,因为这没有任何意义,实现指的是抽象类和他的派生类用来实现自己的对象。 就刚才的例子而言,就是让‘手机’即可以按照品牌来分类,也可以按照功能来分类。
按品牌分类实现结构图:
按软件实现结构图:
由于实现方式多种,桥接模式的核心意图是把这些实现独立出来,让他们各自变化。这就使得每种实现的变化不会影响其他实现,从而达到应对变化的目的。
桥接模式基本代码
桥接模式结构图:
桥接模式代码基本如下:
abstract class Implementor{public abstract void operation();}class ConcreteImplementorA extends Implementor{@Overridepublic void operation() {System.out.println("具体实现A的方法执行");}}class ConcreteImplementorB extends Implementor{@Overridepublic void operation() {System.out.println("具体实现B的方法执行");}}abstract class Abstraction{protected Implementor implementor;public void setImplementor(Implementor implementor){this.implementor = implementor;}public abstract void operation();}class RefinedAbstraction extends Abstraction{@Overridepublic void operation() {System.out.println("具体的Abstraction");implementor.operation();}}public static void main(String[] args) {Abstraction ab;ab = new RefinedAbstraction();ab.setImplementor(new ConcreteImplementorA());ab.operation();ab.setImplementor(new ConcreteImplementorB());ab.operation();}
桥接模式所说的‘将抽象部分与它的实现分离’,其实就是实现系统可能有多角度分类,每一种分类都与有可能变化,那么就把这种多角度分离出来让他们独立变化,减少他们的耦合。 也就是说,当我们需要多角度去分类实现对象,而只用继承会造成大量的类增加,不能满足开放-封闭原则,这时就需要考虑用桥接模式了。
总结
以上就是本文全部内容,本文主要向大家介绍了设计模式中的桥接模式,通过手机软件与手机系统不匹配实例开篇,之后介绍合成/聚合复用原则,最后引出桥接模式基本代码,并改造案例代码为桥接模式。感谢各位能够看到最后,如有问题,欢迎各位大佬在评论区指正,希望大家可以有所收获!创作不易,希望大家多多支持!