概述
结构型设计模式
结构型设计模式关注如何将类或对象按照某种方式组合成更大的结构,以解决系统结构和对象之间的耦合问题。其中,桥接模式(Bridge Pattern)是一种重要的结构型设计模式,它通过将抽象部分与实现部分分离,以便二者可以独立地变化。
桥接模式的定义
桥接模式是一种用于将抽象部分与实现部分分离的设计模式。它的主要目的是通过将抽象和实现独立地变化,以便二者可以独立地扩展。桥接模式通过将抽象部分和实现部分解耦,使它们可以独立地变化,从而提高代码的灵活性和可扩展性。
桥接模式的角色和关系
- Abstraction(抽象部分):定义了高层逻辑,维护一个指向Implementor对象的引用。
- RefinedAbstraction(被提炼的抽象):对Abstraction进行扩展,可以增加额外的操作或功能。
- Implementor(实现部分):定义了具体实现的接口,提供了基本操作的方法。
- ConcreteImplementor(具体实现部分):实现Implementor接口,提供具体实现。
版本迭代
业务需求
某品牌手机想安装一款游戏
紧耦合版
public class HandsetBrandNGame { public void run(){ System.out.println("运行N品牌手机游戏 "); } } //客户端 public class Client { public static void main(String[] args) { HandsetBrandNGame game=new HandsetBrandNGame(); game.run(); } }
增加品牌
新的需求是两个品牌的手机都要安装游戏
要增加父类了
//父类 public class HandsetGame { public void run(){} } //N品牌手机 public class HandsetBrandNGame extends HandsetGame{ public void run(){ System.out.println("运行N品牌手机游戏"); } } //m品牌手机 public class HandsetBrandMGame extends HandsetGame{ public void run(){ System.out.println("运行M品牌手机游戏"); } } //客户端 public class Client { public static void main(String[] args) { HandsetGame M=new HandsetBrandMGame(); M.run(); HandsetGame N=new HandsetBrandNGame(); N.run(); } }
当你发现代码写起来太简单的时候,那么问题就来喽
两个品牌两款软件
新的需求是两个品牌的手机都安装游戏和通讯录
//手机父类 public class HandsetBrand { public void run(){ } } //M品牌手机 public class HandsetBrandM extends HandsetBrand { } //N品牌手机 public class HandsetBrandN extends HandsetBrand { }
写到这有没有发现两个子类完全继承父类方法,没有扩充也没有重写,这就很有问题
//游戏 public class HandsetBrandMGame extends HandsetBrandM { public void run(){ System.out.println("运行M品牌手机游戏"); } } public class HandsetBrandNGame extends HandsetBrandM { public void run(){ System.out.println("运行N品牌手机游戏"); } } //通讯录 public class HandsetBrandMAddressList extends HandsetBrandM { public void run(){ System.out.println("运行M品牌手机通讯录"); } } public class HandsetBrandNAddressList extends HandsetBrandM { public void run(){ System.out.println("运行N品牌手机通讯录"); } } //客户端 public class Client { 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(); } }
这是从手机品牌的维度去设计,如果从软件的维度去设计,类图如下
可以看到和上面的类图一样,只是父类变成了软件,子类变成了游戏和通讯录
换个中文的图可能理解更清晰
这两种分类只是从两个角度,但是对于增加新的品牌或者增加新的软件来说都需要增加一坨(这个词有没有画面,哈哈),对于软件设计来说都不是好的方向。
两个毫不相干的功能想掺和到一起,通常可以考虑用继承去实现,继承作用的一个角度就是增加新的功能,但是对象的继承关系是在编译时就定义好了,无法在运行时改变从父类继承的实现。子类的实现与它的父类有非常紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化。当需要复用子类时,如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。
那有其他办法可以解决这种通过继承而增加新的功能的方法吗,可以考虑组合聚合关系。
组合聚合都是关联的特殊种类。聚合表示一种弱的‘拥有’关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分:组合则是一种强的‘拥有’关系,体现了严格的部分和整体的关系,部分和整体的生命周期一样。那么接下来一版就通过聚合关系来解决扩充复用和减弱耦合的问题.
松耦合的设计
手机软件
//手机软件父类 public abstract class HandsetSoft { public abstract void run(); } //具体软件子类-通讯录 public class HandsetAddressList extends HandsetSoft{ @Override public void run() { System.out.println("通讯录"); } } //游戏 public class HandsetGame extends HandsetSoft{ @Override public void run() { System.out.println("手机游戏"); } }
手机品牌
//手机品牌父类 public abstract class HandsetBrand { protected HandsetSoft soft; //设置手机软件 public void setHandsetSoft(HandsetSoft soft){ this.soft=soft; } //运行 public abstract void run(); } //具体品牌子类 public class HandsetBrandM extends HandsetBrand{ @Override public void run() { System.out.println("品牌M"); soft.run(); } } public class HandsetBrandN extends HandsetBrand{ @Override public void run() { System.out.println("品牌N"); soft.run(); } }
客户端
public class Client { public static void main(String[] args) { HandsetBrand ab; ab=new HandsetBrandM();//M品牌 ab.setHandsetSoft(new HandsetGame()); ab.run(); ab.setHandsetSoft(new HandsetAddressList()); ab.run(); HandsetBrand ab2; ab2=new HandsetBrandN();//N品牌 ab2.setHandsetSoft(new HandsetGame()); ab2.run(); ab2.setHandsetSoft(new HandsetAddressList()); ab2.run(); } }
版本迭代业务分析总结
从紧耦合到使用桥接模式,是从有限到无限思维的变化,最后再来看一下手机和软件这个业务,使用桥接模式的业务需求主要是解决手机软件和手机品牌之间的耦合关系,以实现它们可以独立地变化和扩展。具体来说,手机软件和手机品牌使用桥接模式的业务需求包括以下几个方面:
- 独立变化:手机软件和手机品牌属于两个不同的维度,它们可能会因为不同的需求而需要独立变化。比如,手机软件可能需要在不同的手机品牌上运行,而手机品牌可能需要支持不同的手机软件。使用桥接模式可以让它们之间的变化相互独立,不会相互影响。
- 灵活组合:用户希望能够根据个人喜好和需求自由地组合手机软件和手机品牌,而不受限于固定的组合方式。使用桥接模式可以让用户根据需要灵活地组合不同的手机软件和手机品牌,而不需要为每一种组合编写大量的代码。
- 代码复用:桥接模式能够提高代码的复用性,避免在不同的手机软件和手机品牌组合下重复编写相似的代码。通过将抽象部分与实现部分分离,可以更好地复用已有的代码,减少开发成本和维护成本。
- 扩展性:随着新的手机软件和手机品牌不断涌现,系统需要具有良好的扩展性,能够方便地加入新的手机软件和手机品牌。使用桥接模式可以使系统具有较高的扩展性,能够快速、灵活地适应新的需求。
总的来说,手机软件和手机品牌使用桥接模式的业务需求是为了实现它们之间的独立变化、灵活组合、代码复用和良好的扩展性,从而提高系统的灵活性、可维护性和可扩展性。
问题升华
抽象与实现
桥接概念中的“通过将抽象部分和实现部分解耦,使它们可以独立地变化”什么意思呢,抽象与它的实现分离,这并不是说,让抽象类与其派生类分离,因为这没有任何意义。实现指的是抽象类和它的派生类用来实现自己的对象。就刚才的例子而言,就是让‘手机’既可以按照品牌来分类,也可以按照功能来分类。
再专业一点解释"抽象" 和 “实现” 这两个术语是:
- 抽象(Abstraction):代表系统中的高层结构,它提供了一组抽象接口,并维护对实现部分的引用。抽象可以包含一些方法或属性,这些方法或属性依赖于实现部分的接口。
- 实现(Implementation):代表抽象接口的实现部分,它定义了实现部分的接口。实现部分通常提供了一组具体的操作,这些操作被抽象部分所引用和使用。
举例来说,假设要设计一个绘制图形的系统,其中抽象部分可以是各种形状(如圆形、矩形等),而实现部分可以是各种颜色(如红色、蓝色等)。这样,就可以将抽象的形状和实现的颜色进行组合,从而实现不同形状和不同颜色的组合绘制。
除了上面提到的手机的例子,生活中还有许多常见的例子:
比如桥接模式可以应用于多媒体播放器,其中抽象部分可以是各种播放器类型(如音频播放器、视频播放器),而实现部分可以是各种操作系统平台(如Windows、MacOS、Linux)。
也可以应用于汽车的制造,其中抽象部分可以是汽车的类型(如轿车、卡车),而实现部分可以是发动机类型(如汽油发动机、电动发动机)。
抽象包含的一些方法或属性依赖于实现部分的接口
举个例子,假设有一个形状的抽象类 Shape,它包含一个绘制的抽象方法:
public abstract class Shape { protected DrawAPI drawAPI; protected Shape(DrawAPI drawAPI) { this.drawAPI = drawAPI; } public abstract void draw(); }
在这里,Shape 类中的 draw 方法是一个抽象方法,它依赖于 DrawAPI 接口,但并没有具体的实现。DrawAPI 接口代表了实现部分的接口,它定义了绘制的方法:
public interface DrawAPI { void draw(int x, int y); }
具体的形状类(如 Circle、Square)会继承 Shape 类并实现 draw 方法,而在实现 draw 方法时,会调用 DrawAPI 接口的方法来完成具体的绘制操作。
通过这种方式,抽象部分中的方法依赖于实现部分的接口,具体的实现则由实现部分来完成,从而实现了抽象部分与实现部分的解耦。这样的设计使得我们可以根据需要灵活地变更和扩展具体的实现部分,而不会影响到抽象部分的设计和使用。
关联关系与桥接模式
抽象部分中的方法依赖于实现部分的接口,可以通过关联关系来实现,但它也是桥接模式的一个特点。
在桥接模式中,抽象部分和实现部分之间存在关联关系,但它们又可以独立地变化和扩展。具体来说,抽象部分提供了一组抽象接口,而实现部分定义了具体的实现。抽象部分通过引用实现部分的接口来使用具体的实现。
关联关系是桥接模式的一种实现方式,通过关联关系,抽象部分和实现部分之间建立了联系。但桥接模式并不仅限于关联关系,它更重要的是将抽象部分和实现部分解耦,使它们可以独立地变化。
在桥接模式中,=可以根据需要灵活地变更和扩展抽象部分和实现部分,而不会相互影响,因此,当抽象部分中的方法依赖于实现部分的接口,并且能够实现抽象部分和实现部分的解耦时,可以说符合桥接模式的设计原则。关联关系是一种常见的实现方式,但并不是唯一的方式。
桥接模式适合情况
一个产品只要有不同的分类方式,就可以考虑用桥接。
当一个类存在两个独立变化的维度时,可以使用桥接模式来将这两个维度分离,使它们可以独立地变化。例如,一个产品可以按照品牌和功能进行分类,这样就可以使用桥接模式将品牌和功能这两个维度进行分离,使它们可以独立地变化。
当一个类需要在多个维度上进行扩展,且不希望使用多层继承结构时,可以考虑使用桥接模式。桥接模式可以避免多层继承结构带来的复杂性和耦合度,使得系统更加灵活和可扩展。
谁是实现,谁是抽象
上面提到多个维度扩展,那如何定义谁是抽象谁是实现呢,就好比上面的手机品牌和手机软件
确定抽象和实现的关系取决于我们希望如何组织和管理不同维度的变化。一般来说,可以根据以下几点来确定哪种是抽象,哪种是实现:
稳定性和变化性:如果一个维度的变化相对稳定且不太频繁,而另一个维度的变化比较频繁,通常会将稳定的部分作为抽象,而变化的部分作为实现。
扩展性:如果希望在某个维度上进行扩展,而在另一个维度上保持相对稳定,通常会将需要扩展的部分作为抽象,而相对稳定的部分作为实现。
复用性:如果希望将某个维度的实现应用到多个抽象中,通常会将这个维度的实现作为独立的实现部分。
解耦合:如果希望将不同维度之间进行解耦,使它们可以独立变化,通常会将不同的维度分别作为抽象和实现。
还是上面的例子,可以按照品牌和功能两个维度进行分类。如果认为品牌相对稳定而功能比较容易变化,可以将品牌作为抽象部分,功能作为实现部分;反之,如果功能相对稳定而品牌经常变化,可以将功能作为抽象部分,品牌作为实现部分。
组合聚合的好处
组合聚合原则的好处是,优先使用对象的合成/聚合将有助于保持每个类被封装,并被集中在单个任务上。这样类和类继承层次会保持较小规模,并且不太可能增长为不可控制的庞然大物。
总结一下:
灵活性:通过桥接模式可以让抽象部分和实现部分独立变化,而组合和聚合也能够让对象之间的关系更加灵活。
可维护性:松耦合的设计使得系统更容易维护和扩展。
复用性:桥接模式和组合、聚合都有利于提高代码的复用性,使得系统更易于重用。
相对而言,继承可能会导致类之间的强耦合,而且继承层次的过深会增加系统的复杂性,降低系统的灵活性和可维护性。因此,在很多情况下,桥接模式、组合和聚合往往比继承更具优势。
总结
桥接模式是一种非常有用的设计模式,它可以将抽象和实现独立地变化,从而提高代码的灵活性和可扩展性。在Java语言中,可以使用接口和抽象类来实现桥接模式,更好地组织代码并解决系统结构和对象之间的耦合问题。