🔥 核心
桥接模式把复杂类解耦为两个独立的维度,使得二者可以独立变化。
🙁 问题场景
你是一个粉刷匠。现在有许多 图形(Graph)
需要你去粉刷。
图形多种多样:
红色圆形(RedCircle)
、红色正方形(RedSquare)
、红色三角形(RedTriangle)
蓝色圆形(BlueCircle)
、蓝色正方形(BlueSquare)
、蓝色三角形(BlueTriangle)
绿色圆形(GreenCircle)
、绿色正方形(GreenSquare)
、绿色三角形(GreenTriangle)
…
你需要做出基类 图形(Graph)
的这么多种子类吗?
很显然,继承会造成类数量的爆炸,且扩展起来十分不灵活。
🙂 解决方案
问题的根本原因在于我们企图在两个维度(形状
、颜色
)上去扩展子类。
桥接模式将这两个维度解耦,然后通过组合的方式得到各种各样的图形。
这十分容易做到——我们将 圆形
、正方形
、三角形
抽取到 形状(Shape)
维度,将 红色
、蓝色
、绿色
抽取到 颜色(Color)
维度;然后在 形状
类中添加一个指向 颜色
的成员变量,这样,与颜色相关的工作就可以委派给这个颜色成员变量;这样的引用就是 形状
与 颜色
之间的「桥梁」。
当需要扩展更多的 形状
或者 颜色
时,不需要派生大量的子类,只需要在各自的那一层维度进行一个简单的扩展即可。
嗯嗯,简单而优雅。
但还不止这些。
四人组在编写桥接模式这一章节时,引入了两个晦涩的概念——「抽象」与「实现」。这里的抽象与实现并不和平常代码中的定义一致,而是一种更高层次的描述。
1)抽象部分 定义了两个类层次结构中“控制”部分的接口。它管理着一个指向实现部分层次结构中对象的引用,并会将所有真实工作委派给该对象。
2)实现部分 接口声明了在所有具体实现类中通用的方法。它不需要与抽象接口相匹配。实际上,这两个接口可以完全不一样。通常实现接口只提供原语操作,而抽象接口则会基于这些操作定义较高层次的操作。
在很多情况下,我们只需要将“两个独立的维度”就看成“两个独立的维度”即可,不必区分哪一个维度是“抽象”,哪一个维度是“实现”。
🌈 有趣的例子
日常生活中,一种 遥控器(RemoteController)
只能遥控一种 设备(Device)
。
所以,我们使用桥接模式,从两个独立维度对其进行管理。
我们把 遥控器
作为抽象部分,把 设备(Device)
作为实现部分,前者有着指向后者的成员变量作为桥梁。
设备(接口)
interface Device {
int getVolume();
int getChannel();
void setVolume(int volume);
void setChannel(int channel);
}
收音机
class Radio implements Device {
private int volume = 20;
private int channel = 1;
@Override public int getVolume() { return volume; }
@Override public int getChannel() { return channel; }
@Override public void setVolume(int volume) { this.volume = volume; }
@Override public void setChannel(int channel) { this.channel = channel; }
}
电视
class Tv implements Device {
private int volume = 30;
private int channel = 1;
@Override public int getVolume() { return volume; }
@Override public int getChannel() { return channel; }
@Override public void setVolume(int volume) { this.volume = volume; }
@Override public void setChannel(int channel) { this.channel = channel; }
}
遥控器(抽象基类)
abstract class RemoteController {
protected Device device;
protected RemoteController(Device device) {
this.device = device;
}
public abstract void volumeUp();
public abstract void volumeDown();
public abstract void channelUp();
public abstract void channelDown();
public abstract void showVolume();
public abstract void showChannel();
}
遥控器A类型
class RemoteControllerA extends RemoteController {
protected RemoteControllerA(Device device) {
super(device);
}
@Override
public void volumeUp() {
device.setVolume(device.getVolume() + 10);
}
@Override
public void volumeDown() {
device.setVolume(device.getVolume() - 10);
}
@Override
public void channelUp() {
device.setChannel(device.getChannel() + 1);
}
@Override
public void channelDown() {
device.setChannel(device.getChannel() - 1);
}
@Override
public void showVolume() {
System.out.println("音量:" + device.getVolume());
}
@Override
public void showChannel() {
System.out.println("频道:" + device.getChannel());
}
}
遥控器B类型
class RemoteControllerB extends RemoteController {
protected RemoteControllerB(Device device) {
super(device);
}
@Override
public void volumeUp() {
device.setVolume(device.getVolume() + 15);
}
@Override
public void volumeDown() {
device.setVolume(device.getVolume() - 15);
}
@Override
public void channelUp() {
device.setChannel(device.getChannel() + 2);
}
@Override
public void channelDown() {
device.setChannel(device.getChannel() - 2);
}
@Override
public void showVolume() {
System.out.println("音量:" + device.getVolume());
}
@Override
public void showChannel() {
System.out.println("频道:" + device.getChannel());
}
}
public class BridgePatternDemo {
public static void main(String[] args) {
// 新建一个遥控器(类型A),同时桥接上一个设备(收音机)
RemoteControllerA remoteControllerA = new RemoteControllerA(new Radio());
// 用遥控器调低音量试试!
remoteControllerA.showVolume();
remoteControllerA.volumeDown();
remoteControllerA.showVolume();
// 新建一个遥控器(类型B),同时桥接上一个设备(电视)
RemoteControllerB remoteControllerB = new RemoteControllerB(new Tv());
// 用遥控器增加频道试试!
remoteControllerB.showChannel();
remoteControllerB.channelUp();
remoteControllerB.showChannel();
}
}
音量:20
音量:10
频道:1
频道:3
☘️ 使用场景
◾️如果你想要拆分或重组一个具有多重功能的庞杂类(例如能与多个数据库服务器进行交互的类),可以使用桥接模式。
类的代码行数越多,弄清其运作方式就越困难,对其进行修改所花费的时间就越长。一个功能上的变化可能需要在整个类范围内进行修改,而且常常会产生错误,甚至还会有一些严重的副作用。
桥接模式可以将庞杂类拆分为几个类层次结构。此后,你可以修改任意一个类层次结构而不会影响到其他类层次结构。这种方法可以简化代码的维护工作,并将修改已有代码的风险降到最低。
◾️如果你希望在几个独立维度上扩展一个类,可使用该模式。
桥接建议将每个维度抽取为独立的类层次。初始类将相关工作委派给属于对应类层次的对象,无需自己完成所有工作。
◾️如果你需要在运行时切换不同实现方法,可使用桥接模式。
当然并不是说一定要实现这一点,桥接模式可替换抽象部分中的实现对象,具体操作就和给成员变量赋新值一样简单。
顺便提一句,最后一点是很多人混淆桥接模式和策略模式的主要原因。 记住,设计模式并不仅是一种对类进行组织的方式,它还能用于沟通意图和解决问题。
🧊 实现方式
(1)明确类中独立的维度。独立的概念可能是:抽象/平台,域/基础设施,前端/后端或接口/实现。
(2)了解客户端的业务需求,并在抽象基类中定义它们。
(3)确定在所有平台上都可执行的业务。并在通用实现接口中声明抽象部分所需的业务。
(4)为你域内的所有平台创建实现类,但需确保它们遵循实现部分的接口。
(5)在抽象类中添加指向实现类型的引用成员变量。抽象部分会将大部分工作委派给该成员变量所指向的实现对象。
(6)如果你的高层逻辑有多个变体,则可通过扩展抽象基类为每个变体创建一个精确抽象。
(7)客户端代码必须将实现对象传递给抽象部分的构造函数才能使其能够相互关联。此后,客户端只需与抽象对象进行交互,无需和实现对象打交道。
🎲 优缺点
➕ 你可以创建与平台无关的类和程序。
➕ 客户端代码仅与高层抽象部分进行互动,不会接触到平台的详细信息。
➕ 开闭原则。你可以新增抽象部分和实现部分,且它们之间不会相互影响。
➕ 单一职责原则。抽象部分专注于处理高层逻辑,实现部分处理平台细节。
➖ 对高内聚的类使用该模式可能会让代码更加复杂。
➖ 由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
🌸 补充
对于两个独立变化的维度,使用桥接模式再适合不过了。