这是 2020 年 4 月整理的设计模式专题第五篇。桥接模式是结构型模式里理解门槛较高的一篇——它的核心是识别出系统中两个独立变化的维度,并用组合而不是继承把它们连起来。本文用”银行 × 账户”的例子讲透。
简单说明
桥接模式(Bridge Pattern):将抽象部分与具体实现部分分离,使它们都可以独立地变化。
- 通过组合而不是继承建立两个类的联系;
- 分离抽象部分及其具体实现部分,提高了系统的可扩展性;
- 符合开闭原则,符合合成复用原则;
- 但是增加了系统的理解和设计难度,需要正确识别出系统中两个独立变化的维度。
一句话:当系统存在两个或多个独立变化的维度时,用继承会产生类爆炸,改用组合把它们”桥接”起来。
简单示例
/**
* 描述:账户接口,独立变化的维度之一
*/
public interface Account {
Account openAccount();
void printAccount();
}
/**
* 描述:活期账号
*/
public class CurrentAccount implements Account {
@Override
public Account openAccount() {
System.out.println("打开活期账号");
return this;
}
@Override
public void printAccount() {
System.out.println("活期账号");
}
}
/**
* 描述:定期账号
*/
public class RegularAccount implements Account {
@Override
public Account openAccount() {
System.out.println("打开定期账号");
return this;
}
@Override
public void printAccount() {
System.out.println("定期账号");
}
}
/**
* 描述:银行抽象类,独立变化的维度二,里面组合一个 Account 用于独立的扩展
*/
public abstract class Bank {
protected Account account;
public Bank(Account account) {
this.account = account;
}
protected abstract Account openAccount();
}
/**
* 描述:农业银行
*/
public class ABCBank extends Bank {
public ABCBank(Account account) {
super(account);
}
@Override
protected Account openAccount() {
super.account.printAccount();
return super.account.openAccount();
}
}
/**
* 描述:工商银行
*/
public class ICBCBank extends Bank {
public ICBCBank(Account account) {
super(account);
}
@Override
protected Account openAccount() {
super.account.printAccount();
return super.account.openAccount();
}
}
public class BridgeTest {
public static void main(String[] args) {
// 农业银行的活期账号
Bank abcBank = new ABCBank(new CurrentAccount());
// 工商银行的定期账号
Bank icbcBank = new ICBCBank(new RegularAccount());
abcBank.openAccount();
icbcBank.openAccount();
}
}
两个独立变化的维度:
- 维度一:账户类型(活期 / 定期)——变化的是
Account实现; - 维度二:银行(农行 / 工行)——变化的是
Bank实现。
Bank 通过构造器组合一个 Account,银行与账户自由搭配:农行活期、农行定期、工行活期、工行定期,无需为每个组合建一个类。
为什么不用继承
如果不用桥接,用继承表达”银行 × 账户”:
继承方案(类爆炸):
Bank
/ \
ABCBank ICBCBank
/ \ / \
活期 定期 活期 定期 ← 新增账户类型/银行都要加类
桥接方案(组合):
Bank ──组合──> Account
/ \ / \
ABCBank ICBCBank 活期 定期
- 继承方案:
m种银行 ×n种账户 =m × n个类,类爆炸; - 桥接方案:
m + n个类,线性增长,任意组合自由搭配。
适用场景与优缺点
适用场景:
- 系统存在两个或多个独立变化的维度,且都希望独立扩展;
- 不希望使用继承,或因多层继承导致类数量爆炸;
- 抽象部分和实现部分需要动态切换(运行时换实现)。
优点:
- 抽象与实现分离,两者独立扩展,符合开闭原则;
- 使用组合替代继承,符合合成复用原则;
- 提高了系统的可扩展性和灵活性。
缺点:
- 增加了系统的理解和设计难度;
- 需要正确识别出系统中两个独立变化的维度,识别错了反而增加复杂度。
现实中的应用
| 场景 | 两个独立变化维度 |
|---|---|
| JDBC 驱动 | 驱动类型(MySQL/Oracle) × 平台(Windows/Linux) |
| Java AWT 组件 | 组件类型(Button/TextField) × 外观风格(Windows/Motif) |
| 日志系统 | 日志等级 × 输出目标(控制台/文件/网络) |
| 支付系统 | 支付渠道(支付宝/微信) × 支付方式(扫码/App) |
小结
- 桥接模式将抽象部分与具体实现部分分离,使它们可以独立变化。
- 用组合而不是继承建立联系,两个维度自由搭配,避免类爆炸。
- 关键难点:正确识别两个独立变化的维度。
- 同时符合开闭原则和合成复用原则,但增加了理解与设计难度。
- 一句话:继承是”绑定”两个维度,桥接是”解绑”两个维度。