🧑💻作者:猫十二懿
🎉公众号:猫十二懿
桥接模式
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(); } }
-
Implementor(实现部分):定义实现部分的接口,供抽象部分调用。
/** * @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、桥接模式的总结
从上面的例子也看到了桥接模式的好处,但是也存在着一定的问题
优点:
- 解耦合:桥接模式将 抽象部分和实现部分解耦,使它们可以独立地变化和扩展。抽象部分和实现部分可以独立进行修改,而不会相互影响。
- 扩展性:桥接模式允许在抽象部分和实现部分中分别进行扩展。通过添加新的抽象部分或实现部分的子类,可以很容易地增加新的功能,而不需要修改现有的代码。
- 灵活性:桥接模式提供了一种灵活的设计方式,允许动态地切换和组合抽象部分和实现部分的实现。这使得系统更加灵活和可配置。
缺点:
- 增加复杂性:引入桥接模式会增加一些额外的类和接口,导致系统中的类数量增加,从而增加了代码的复杂性和理解成本。
- 增加开发成本:桥接模式需要对抽象部分和实现部分进行更加细致的设计和管理,这可能需要更多的开发时间和资源。
适用场景:
- 当一个系统需要在多个维度上进行扩展和变化时,可以使用桥接模式来解耦各个维度,使得抽象部分和实现部分可以独立地变化和扩展。
- 当一个抽象类有多个子类,而这些子类又有多个实现类时,可以使用桥接模式将抽象类与实现类解耦,使得抽象类和实现类可以独立地进行变化和扩展。
- 当需要动态地切换抽象部分和实现部分的实现时,桥接模式提供了灵活性,允许在运行时动态地切换和组合不同的实现。
总而言之,桥接模式通过解耦抽象部分和实现部分,提供了灵活性、扩展性和可配置性。它适用于需要在多个维度上进行扩展和变化的系统,并且能够动态地切换和组合不同的实现。但要注意,桥接模式可能增加系统的复杂性和开发成本,需要进行细致的设计和管理。
现。这使得系统更加灵活和可配置。
缺点:
- 增加复杂性:引入桥接模式会增加一些额外的类和接口,导致系统中的类数量增加,从而增加了代码的复杂性和理解成本。
- 增加开发成本:桥接模式需要对抽象部分和实现部分进行更加细致的设计和管理,这可能需要更多的开发时间和资源。
适用场景:
- 当一个系统需要在多个维度上进行扩展和变化时,可以使用桥接模式来解耦各个维度,使得抽象部分和实现部分可以独立地变化和扩展。
- 当一个抽象类有多个子类,而这些子类又有多个实现类时,可以使用桥接模式将抽象类与实现类解耦,使得抽象类和实现类可以独立地进行变化和扩展。
- 当需要动态地切换抽象部分和实现部分的实现时,桥接模式提供了灵活性,允许在运行时动态地切换和组合不同的实现。
总而言之,桥接模式通过解耦抽象部分和实现部分,提供了灵活性、扩展性和可配置性。它适用于需要在多个维度上进行扩展和变化的系统,并且能够动态地切换和组合不同的实现。但要注意,桥接模式可能增加系统的复杂性和开发成本,需要进行细致的设计和管理。