桥接模式
1、桥接模式模式介绍
桥接模式(Bridge Pattern)是一种结构型模式之一。它 通过将抽象部分和实现部分分离,使它们可以独立地变化,从而实现了解耦合的设计。桥接模式使用组合而不是继承的方式来连接抽象和实现,使得两者可以独立地变化,互不影响。
1.1 桥接模式基本实现
在桥接模式中,抽象部分和实现部分分别定义为两个独立的接口(Abstraction 和 Implementor)。抽象部分维护一个指向实现部分的引用,它自身包含了一些基本操作,而这些操作的具体实现则委托给实现部分。这样,抽象部分可以通过调用实现部分的方法来完成具体的功能。
通过桥接模式,可以将一个系统分为多个独立的维度,并且可以独立地对每个维度进行扩展和修改。它提供了更好的灵活性和可扩展性,同时也符合面向对象设计的原则。
桥接模式的主要参与角色:
Abstraction(抽象部分):定义抽象部分的接口,维护一个指向实现部分的引用。
/** * @author Shier * CreateTime 2023/5/17 12:08 */ public abstract class Abstraction { // 聚合 Implementor protected Implementor implementor; public void setImplementor(Implementor implementor) { this.implementor = implementor; } public abstract void operation(); }
RefinedAbstraction(扩充抽象部分):对抽象部分进行扩展,增加新的功能。
/** * @author Shier * CreateTime 2023/5/17 12:09 * 扩充抽象 */ public class RefinedAbstraction extends Abstraction { @Override public void operation() { System.out.println("具体的Abstraction执行了"); implementor.operation(); } }
mplementor(实现部分):定义实现部分的接口,供抽象部分调用。
/** * @author Shier * CreateTime 2023/5/17 12:05 * 实现 */ public abstract class Implementor { public abstract void operation(); }
ConcreteImplementor(具体实现部分):实现实现部分的接口,具体完成具体的功能。
/** * @author Shier * CreateTime 2023/5/17 12:06 * 具体实现A */ public class ConcreteImplementorA extends Implementor { @Override public void operation() { System.out.println("具体实现 A 的方法执行了!"); } } /** * @author Shier * CreateTime 2023/5/17 12:06 * 具体实现B */ public class ConcreteImplementorB extends Implementor { @Override public void operation() { System.out.println("具体实现 B 的方法执行了!"); } }
客户端:
/** * @author Shier * CreateTime 2023/5/17 12:09 */ public class BridgeClient { public static void main(String[] args) { Abstraction abstraction= new RefinedAbstraction(); // 传入A实现类 abstraction.setImplementor(new ConcreteImplementorA()); abstraction.operation(); // 传入B实现类 abstraction.setImplementor(new ConcreteImplementorB()); abstraction.operation(); } }
结果如下:
桥接模式可以应用于很多场景特别是当一个系统需要在多个维度上进行扩展和变化时。例如,当一个抽象类有多个子类,而这些子类又有多个实现类时,可以使用桥接模式将抽象类与实现类解耦,使得它们可以独立地变化和扩展。
桥接模式实现系统可能有多角度分类,每一种分类都有可能变化, 那么就把这种多角度分离出来让它们独立变化,减少它们之间的耦合。
2、具体案例说明
案例背景:在手机问世之初,存在很多的限制,两个不同品牌手机不能使用同一个应用,也就是存在兼容的问题,很多APP都是各自生产的,只能兼容自家的手机硬件。那么两个品牌手机,都有游戏,我觉得从面向对象的思想来说,应该有一个父类 ‘手机品牌游戏’,然后让N和M品牌的手机游戏都继承于它,这样可以实现同样的运行方法。
同时,由于手机都需要通讯录功能,于是N品牌和M品牌都增加了通讯录的增删改查功能。
2.1 不使用桥接模式实现
会出现紧耦合的问题
先看看代码结构图:
父类应该是 ‘手机品牌’ ,下有 ‘手机品牌M’ 和 ‘手机品牌N’,每个子类下各有 ‘通讯录’ 和 ‘游戏’ 子类。
手机类:
/** * @author Shier * CreateTime 2023/5/17 16:54 * 手机品牌 */ public class PhoneBrand { public void run(){} }
手机品牌N和手机品牌M类:
/** * @author Shier * CreateTime 2023/5/17 16:55 * 手机品牌M */ public class PhoneBrandM extends PhoneBrand{ } /** * @author Shier * CreateTime 2023/5/17 16:55 * 手机品牌N */ public class PhoneBrandN extends PhoneBrand{ }
下属的各自通讯录类和游戏类:
/** * @author Shier * CreateTime 2023/5/17 16:56 * 游戏 */ public class PhoneBrandMGame extends PhoneBrandM { public void run(){ System.out.println("执行M品牌手机游戏"); } } public class PhoneBrandNGame extends PhoneBrandN { public void run(){ System.out.println("执行N品牌手机游戏"); } }
/** * @author Shier * CreateTime 2023/5/17 16:57 * 通讯录 */ public class PhoneBrandMAddressList extends PhoneBrandM{ public void run(){ System.out.println("运行M品牌手机通讯录"); } } public class PhoneBrandNAddressList extends PhoneBrandN { public void run() { System.out.println("运行N品牌手机通讯录"); } }
客户端代码:
/** * @author Shier * CreateTime 2023/5/17 16:59 */ public class PhoneClient { public static void main(String[] args) { // 调用M手机游戏 PhoneBrand mGame = new PhoneBrandMGame(); mGame.run(); // 调用M手机通讯录 PhoneBrand mAddressList= new PhoneBrandMAddressList(); mAddressList.run(); // 调用N手机游戏 PhoneBrand nGame = new PhoneBrandNGame(); nGame.run(); // 调用N手机通讯录 PhoneBrand nAddressList= new PhoneBrandNAddressList(); nAddressList.run(); } }
功能来说,算是实现了,但是要是再增加一个音乐播放呢?
在每个品牌的下面都增加一个子类。但是这些子类的却别并不是很大。
如果又有了新的手机品牌,它同样有以上的功能,就得再增加这些重复的子类。
我们一直在用面向对象的理论设计的,先有一个品牌,然后多个品牌就抽象出一个品牌抽象类,对于每个功能,就都继承各自的品牌。或者,不从品牌,从手机软件的角度去分类,这有什么问题呢?
是呀,就像我们刚开始学会用面向对象的继承时,感觉它既新颖又功能强大,所以只要可以用,就都用上继承。这就好比是有了新锤子,所有的东西看上去都成了钉子。但事实上,很多情况用继承会带来麻烦。类之间的耦合度很高,修改一个类牵涉到其他的类
比如,对象的继承关系是在编译时就定义好了,所以无法在运行时改变从父类继承的 实现。子类的实现与它的父类有非常紧密的依赖关系,以至于父类实现中的 任何变化必然会导致子类发生变化。当你需要复用子类时,如果继承下来的 实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。这种 依赖关系限制了灵活性并最终限制了复用性。
这样的继承结构,如果不断地增加新品牌或新功能,类会越来越多的。
这里就要使用到 合成 / 聚合复用原则 :尽量使用合成/聚合,尽量不要使用 类继承。
2.2 使用桥接模式实现
使用的 合成 / 聚合复用模式 的结构图:
手机品牌包含手机软件,但软件并不是品牌的一部分,所以它们之间是聚合关系。
手机软件抽象类:
/** * @author Shier * CreateTime 2023/5/17 17:14 * 手机软件类 */ public abstract class PhoneSoft { // 执行 public abstract void run(); }
游戏、通讯录等具体类:
/** * @author Shier * CreateTime 2023/5/17 17:15 * 手机游戏 */ public class PhoneGame extends PhoneSoft{ @Override public void run() { System.out.println("手机游戏"); } }
/** * @author Shier * CreateTime 2023/5/17 17:16 * 手机通讯录 */ public class PhoneAddressList extends PhoneSoft { @Override public void run() { System.out.println("手机通讯录"); } }
手机品牌类:
/** * @author Shier * CreateTime 2023/5/17 17:17 * 手机品牌 */ public abstract class PhoneBrand { protected PhoneSoft phoneSoft; /** * 设置手机软件 * * @param phoneSoft */ public void setPhoneSoft(PhoneSoft phoneSoft) { this.phoneSoft = phoneSoft; } /** * 执行 */ public abstract void run(); }
手机品牌具体类M、N:
/** * @author Shier * CreateTime 2023/5/17 16:55 * 手机品牌M */ public class PhoneBrandM extends PhoneBrand { @Override public void run() { System.out.print("品牌M"); phoneSoft.run(); } }
/** * @author Shier * CreateTime 2023/5/17 16:55 * 手机品牌N */ public class PhoneBrandN extends PhoneBrand { @Override public void run() { System.out.print("品牌N"); phoneSoft.run(); } }
客户端代码:
/** * @author Shier * CreateTime 2023/5/17 16:59 */ public class PhoneClient { public static void main(String[] args) { // 调用 M 品牌手机 PhoneBrand brandM = new PhoneBrandM(); // 调用通用的手机游戏 brandM.setPhoneSoft(new PhoneGame()); brandM.run(); // 通讯录 brandM.setPhoneSoft(new PhoneAddressList()); brandM.run(); // 调用N品牌手机 PhoneBrand brandN = new PhoneBrandN(); // 手机游戏 brandN.setPhoneSoft(new PhoneGame()); brandN.run(); // 通讯录 brandN.setPhoneSoft(new PhoneAddressList()); brandN.run(); } }
输出结果:
相比之前不使用桥接模式和合成/聚合复用原则时,代码更加清晰了许多,而且如果需要新增一个功能时,比如手机音乐播放功能,那么只要增 加这个类就行了。不会影响其他任何类。类的个数增加也只是一个。
/** * @author Shier * CreateTime 2023/5/17 17:30 * 手机播放音乐 */ public class PhoneMusicPlay extends PhoneSoft { @Override public void run() { System.out.println("播放音乐"); } }
如果是要增加S品牌,只需要增加一个品牌子类就可以了。个数也是一个,不会影响其他类的改动。
/** * @author Shier * CreateTime 2023/5/17 17:31 * 手机品牌S */ public class PhoneBrandS extends PhoneBrand { @Override public void run() { System.out.print("手机品牌S"); } }
这也符合了之前的开放-封闭原则。这样的设计显然不会修改原来的代码,而只是扩展类就行了。合成/聚合复用原则是优先使用对象的合成或聚合,而不是类继承。
3、桥接模式的总结
从上面的例子也看到了桥接模式的好处,但是也存在着一定的问题
优点:
解耦合:桥接模式将 抽象部分和实现部分解耦,使它们可以独立地变化和扩展。抽象部分和实现部分可以独立进行修改,而不会相互影响。
扩展性:桥接模式允许在抽象部分和实现部分中分别进行扩展。通过添加新的抽象部分或实现部分的子类,可以很容易地增加新的功能,而不需要修改现有的代码。
灵活性:桥接模式提供了一种灵活的设计方式,允许动态地切换和组合抽象部分和实现部分的实现。这使得系统更加灵活和可配置。
缺点:
增加复杂性:引入桥接模式会增加一些额外的类和接口,导致系统中的类数量增加,从而增加了代码的复杂性和理解成本。
增加开发成本:桥接模式需要对抽象部分和实现部分进行更加细致的设计和管理,这可能需要更多的开发时间和资源。
适用场景:
当一个系统需要在多个维度上进行扩展和变化时,可以使用桥接模式来解耦各个维度,使得抽象部分和实现部分可以独立地变化和扩展。
当一个抽象类有多个子类,而这些子类又有多个实现类时,可以使用桥接模式将抽象类与实现类解耦,使得抽象类和实现类可以独立地进行变化和扩展。
当需要动态地切换抽象部分和实现部分的实现时,桥接模式提供了灵活性,允许在运行时动态地切换和组合不同的实现。
总而言之,桥接模式通过解耦抽象部分和实现部分,提供了灵活性、扩展性和可配置性。它适用于需要在多个维度上进行扩展和变化的系统,并且能够动态地切换和组合不同的实现。但要注意,桥接模式可能增加系统的复杂性和开发成本,需要进行细致的设计和管理。
现。这使得系统更加灵活和可配置。
缺点:
增加复杂性:引入桥接模式会增加一些额外的类和接口,导致系统中的类数量增加,从而增加了代码的复杂性和理解成本。
增加开发成本:桥接模式需要对抽象部分和实现部分进行更加细致的设计和管理,这可能需要更多的开发时间和资源。
适用场景:
当一个系统需要在多个维度上进行扩展和变化时,可以使用桥接模式来解耦各个维度,使得抽象部分和实现部分可以独立地变化和扩展。
当一个抽象类有多个子类,而这些子类又有多个实现类时,可以使用桥接模式将抽象类与实现类解耦,使得抽象类和实现类可以独立地进行变化和扩展。
当需要动态地切换抽象部分和实现部分的实现时,桥接模式提供了灵活性,允许在运行时动态地切换和组合不同的实现。
总而言之,桥接模式通过解耦抽象部分和实现部分,提供了灵活性、扩展性和可配置性。它适用于需要在多个维度上进行扩展和变化的系统,并且能够动态地切换和组合不同的实现。但要注意,桥接模式可能增加系统的复杂性和开发成本,需要进行细致的设计和管理。