桥接模式------搭建抽象与实现之间的连接
📚 A公司需要开发一款新的桌面应用程序,需要为此程序设计用户界面,这个程序需要支持在Windows、MacOS和Linux操作系统上运行,而且需要在每一种系统实现对应的用户界面元素,比如:按钮、文本框和窗口等。
🧠 对于上述问题,我们很容易就能想到解决办法------我们可以设置一个按钮类,让Windows、MacOS和Linux的按钮都继承这个按钮类,然后实现具体的属性和方法,文本框和窗口也使用同样的方法,如下图。
看到这个图片你有没有感觉头皮发麻~😂这个类图中采用了多层继承结构,导致类的个数成倍增长。
- 当新增一个元素时,我们需要新增一个继承结构;
- 当新增一个操作系统时,我们需要对每一种元素都创建一种新的操作系统平台;
🙋♂️ 上述问题就需要我们这篇文章讲解的
桥接模式
来解决,减少系统开销,使扩展时更加简单。
1. 概述
桥接模式
就是将抽象部分与其实现部分分离,使他们可以独立的变化,这种分离允许抽象部分和实现部分可以独立地扩展、修改和重用,从而提高了系统的灵活性、可扩展性和可维护性。
❓ 什么是抽象部分? 什么又是实现部分呢?
🏷️ 桥接模式就是找出系统中发生变化的两个维度,将这两个维度分离出来,使两者可以进行扩展,然后将两者连接起来,像一个桥一样,它们之间可以协同工作,又可以独立变化。这两个维度可以定义为
类的功能层次
和类的实现层次
。
1.1 类的功能层次(Abstraction)
- 类的功能层次定义了系统的高层结构和功能,它描述了系统的抽象接口和功能。在桥接模式中,类的功能层次通常由一个抽象类或者接口来表示,它定义了系统的抽象接口,描述了系统的功能和行为。
- 类的功能层次不关心具体的实现细节,只关注系统的抽象接口和功能。它定义了系统的核心功能,但不涉及具体的技术实现或平台细节。
- 类的功能层次通过桥接接口与实现层次进行通信,从而实现了抽象和实现的分离。
一般来说,我们将与这个类的一些功能业务方法和与这个类有关的最密切的维度定义为类的功能层次;
1. 2 类的实现层次(Implementor)
- 类的实现层次定义了系统的具体实现细节,它描述了系统的实现部分和具体技术细节。在桥接模式中,类的实现层次通常由一个或多个具体实现类来表示,它们实现了类的功能层次定义的抽象接口。
- 类的实现层次负责实现类的功能层次定义的抽象接口,并提供具体的技术实现或平台细节。它包含了系统的具体实现逻辑,但与抽象接口无关。
- 类的实现层次可以根据需要进行扩展和修改,而不会影响类的功能层次。它通过实现类的功能层次定义的抽象接口来与功能层次进行通信,实现了抽象和实现的解耦合。
💯 开头的案例中,我们发现了两个变化的维度------
元素
和操作系统
,在这个系统中,我们具体的实现功能是和操作系统有关的,所以实现部分应该是操作系统
,而在我们的系统中,主要是做一些元素的显示功能,这个维度是和我们的功能业务关系最密切的,所以抽象部分应该是元素
。
2. 结构
桥接模式由以下角色组成:
- 抽象类(Abstraction): 抽象类定义了系统的高层结构和功能,其中定义了实现类接口类型的对象,并定义了抽象方法或者抽象属性,以便于与实现部分进行交互;
- 扩充抽象类(Refined Abstraction): 扩充由抽象类定义的接口,并扩展了抽象类中定义的方法或者属性,可以调用实现类中定义的业务方法;
- 实现类接口(Implementor): 定义实现类的接口,定义了实现抽象类的接口的方法;
- 具体实现类(Concrete Implementor): 具体实现了实现类接口的方法,它是实现部分的具体实现,负责实现抽象类定义的功能。
3. UML图
4. 实现
csharp
// 抽象类
abstract class Abstraction {
// 实现类接口的引用
private Implementor impl;
public void setImpl(Implementor impl) {
this.impl = impl;
}
// 定义公共的业务方法
public abstract void oper();
}
scala
class RefinedAbstraction extends Abstraction {
// 实现抽象类中的业务方法
public void oper() {
impl.operImpl(); // 调用实现类的实现方法
}
}
csharp
// 实现类的接口
interface Implementor {
void operImpl();
}
typescript
// 具体实现类
class ConcreteImplementor implements Implementor {
public void operImpl() {
// 具体实现的方法
}
}
5. 案例分析
我们在
结构
最后分析了开头案例的两个维度,使用元素作为我们的抽象部分,使用操作系统平台作为我们的实现部分,这节我们就开始实现功能代码吧。
0️⃣ 定义一个元素类的抽象类
csharp
public abstract class Element {
public ElementImpl elementImpl;
public void setElementImpl(ElementImpl elementImpl) {
this.elementImpl = elementImpl;
}
public abstract void showElement();
}
1️⃣ 定义元素的扩充抽象类
scala
public class ButtonElement extends Element {
@Override
public void showElement() {
elementImpl.show();
System.out.println("Button");
}
}
public class TextBoxElement extends Element {
@Override
public void showElement() {
elementImpl.show();
System.out.println("TextBox");
}
}
public class WindowElement extends Element {
@Override
public void showElement() {
elementImpl.show();
System.out.println("Window");
}
}
2️⃣ 定义实现类接口
csharp
public interface ElementImpl {
void show();
}
3️⃣ 定义具体实现类
typescript
public class WindowsImpl implements ElementImpl {
@Override
public void show() {
System.out.println("Windows 展示:");
}
}
public class MacImpl implements ElementImpl {
@Override
public void show() {
System.out.println("MacOS 展示:");
}
}
public class LinuxImpl implements ElementImpl {
@Override
public void show() {
System.out.println("Linux 展示:");
}
}
📖 使用这种方式,如果我们新增一个操作系统,我们只需要用新的操作系统实现
ElementImpl
接口,就可以了;如果我们新增一个元素,我们只需要用新的元素继承Element
抽象类即可。这样比上面一开始那种多层继承结构方便多了。
6. 优缺点
✅️ 优点:
分离抽象和实现部分。
通过使系统中抽象部分和实现部分分离开来,使可以独立变化,这样系统的抽象部分和实现部分可以单独扩展、修改和重用,提高了系统的灵活性和可维护性;抽象部分和实现部分解耦。
由于实现部分和抽象部分独立出来,抽象部分不知道具体的实现细节,降低系统复杂性;
❌ 缺点:
增加系统复杂度。
在设计和理解系统时,需要识别系统中抽象部分和实现部分的关系,识别这个关系需要有一定的经验积累;
7. 使用场景
- 如果系统中存在多个维度的变化,可以使用桥接模式来处理;
- 当需要在抽象和实现层次上都分别进行扩展时,桥接模式因为对两个维度进行解耦,可以很方便实现扩展;
- 对不希望使用继承或者多继承导致系统中类的个数急剧增加时可以使用桥接模式。
8. 总结
在软件开发的过程中,我们如果遇到一个类或者一个系统有多个维度的变化,都可以尝试使用桥接模式进行系统设计。学会识别两个维度的功能变化和实现变化,积累自己设计经验哦~
欢迎朋友们关注我的公众号📢📢📢:【码匠er】
我会持续更新关于技术的文章❤️🤎💚🧡💛
欢迎大家点赞👍 收藏 ⭐ 关注 💡三连支持一下~~~
查看文章过程中有问题或者有需要修改的地方,欢迎私聊我哦 🗨🗨🗨
不管世界变成什么样,我们都要加强自己自身能力~✊✊✊