定义
桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。
就是用来解决多维度变化的问题
拿T恤举例,T恤根据印花的分类,有局部印花,全身印花,刺绣,然后又有不同的尺码,每种类型都有 S,M,L。所以T恤有了两个维度的变化,一个维度印花,一个维度尺码
如果我们用多重继承的方式来实现T恤,我们会先继承T恤,生成三种印花T,然后每种印花T又各自派生三种尺码的T
桥接模式-继承实现.png关于制定尺寸是有一套统一的规则的,和 T 恤印花类型并没有的关联,多重继承的实现,每个类都要走一遍不同 size 的实现,会出现代码冗余。而且实现所有款式和尺码的衣服,一共需要1+3+3*3=13个类。而且一旦每增加一个尺码,每种印花类型又要派生多个子类,类的数量增长很快
现在我们用桥接模式来改造它,把这两个维度拆开,独立两套继承体系,然后在使用的时候组合在一起。首先以 T 恤的印花为主,一个继承体系,有三种子类。然后尺码在T恤类中是抽象类型的,不涉及具体的实现
桥接模式-组合实现.png而具体的实现由程序运行时动态指定。于是我们有 1+3+1+3=8个类,就可以实现所有尺寸的 T 恤。
同时,把这两个维度剥离开,形成两套继承体系单独维护。把类之间的静态继承关系,转化为动态组合关系。两个维度单独变化,两个维度的关联采用抽象的方式连接,动态去实现,降低了耦合,同时容易扩展
这样,两个单独的继承体系通过某个抽象结构连接在一起,就称为桥接。是组合优于继承的典型模式
简单设计
抽象类
有具体的业务也可以又抽象的业务方法
维持对实现类接口的一个引用
抽象实现类
实现抽象类的抽象方法
实现类接口
定义基本操作,具体实现交给子类
具体实现类
实现基本操作
简单的类图如下
桥接模式-类图.png运行时,为 Abstraction 的具体实例,动态组合 Implemetor 的具体实例,两者桥接在一起
这两个维度单独变化,对任何一个维度的扩展都不会干扰到另一个维度
应用实例
菜单
假设我们有这样一个需求,在一个页面有三个菜单,菜单 A,菜单 B 和 菜单 C。菜单 A 和 菜单 B 有不同的 UI。菜单 C 只比 菜单 B 多了一个菜单选项,其他都一样
每个菜单都有多个菜单选项,菜单中会有相同的菜单选项。菜单 A 有点赞、举报;菜单 B 有举报、删除;菜单 C 有点赞、举报、删除
菜单选项有这几种,点赞、举报、删除
菜单 B 还会随着服务端返回的数据有变化,比如根据数据增加一个点赞的选项。每个菜单选项都会执行相应的业务
后面产品需求的变化,各个菜单的菜单选项可能发生增减,而每种菜单项内部的实现也可能发生变化
如果采用多重继承,没增加或者减少一个菜单项,都当成一个新类型的菜单,这样静态继承创建菜单的方式,会让类数量变多。而任何一个菜单选项实现的改变,又会影响拥有该类型选项的菜单代码。维护起来很艰难
现在我们考虑用桥接模式来改造它
总结以上需求,我们这里有两个维度的变化:菜单UI+菜单项。UI 只有两种,A 风格和 B风格。菜单项有三种,点赞、举报、删除。那么就以菜单 UI 这个维度为菜单抽象类的基础,形成一个菜单继承体系;菜单项单独出去形成另一个维度。使用的时候,再去动态设置菜单选项
桥接模式-菜单.png在使用菜单的地方,动态地菜添加菜单项的实现
// 创建 A 菜单
AbsMenu menu = new MenuA();
menu.addMenu(new PraiseMenuItem())
menu.addMenu(new ReportMenuItem());
// 创建 B 菜单
AbsMenu menu = new MenuB();
menu.addMenu(new ReportMenuItem());
menu.addMenu(new DeleteMenuItem());
// 创建 C 菜单,C 菜单和 B 菜单 UI 一致,只是多了个菜单项
AbsMenu menu = new MenuC();
menu.addMenu(new PraiseMenuItem());
menu.addMenu(new ReportMenuItem());
menu.addMenu(new DeleteMenuItem());
产品需求变化了,B 菜单不再有举报了,我们在配置那个菜单时只移出 ReportMenuItem 就可以了
如果又多了一种 D 类型的菜单,还是这几种菜单项,只是 UI 风格不再和 A、B 相同,只需要继承 AbsMenu 创建 D 风格菜单。然后菜单项同样动态组合进来
多用组合,少用继承,代码可以变得灵活。以后需求变化了,也可以减少改动量
JDBC
JDBC 针对 Java 平台,对底层数据库做了一个统一的封装,也是一套规范。各个数据库厂商自己去实现
比如我们对一个数据库查询,可以使用下面的简单模板
public class TestJDBC {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
// 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 打开连接
connection = DriverManager.getConnection("jdbc:mysql://localhost/test", "root", "root");
// 执行查询
statement = connection.createStatement();
resultSet = statement.executeQuery("select * from test");
while (resultSet.next()) {
System.out.println(resultSet.getString("name"));
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
这个 JDBC 的核心类有
- DriverManager
- Connection
- Driver
...
观察上面的数据库连接过程,发现我们面向的是 JDBC 的 API 进行编程,而具体的实现通过各个厂商生产的驱动程序
总整体上看,JDBC 也是桥接模式的一种。JDBC 的 API 和数据库查询过程、应用程序为抽象部分,而各个厂商实现的驱动为实现部分。这两套独立变化,可以在运行时为抽象选择实现,将两者桥接在一起
桥接通过 DriverManager 来实现,两个维度,API 的应用和驱动程序的实现,各自形成一套继承体系,在运行时桥接在一起
比如 A 应用,可以用 Oracle,也可以用 MySQL,只需要更换驱动了,应用内的代码基本不用改动
桥接模式-JDBC.png扩展思考
这里只处理了两个维度,如果有多维度变化怎么办?
那就再加个实现,然后与当前抽象桥接在一起
还是拿 T 恤举例,这时候,我们又多了一个新的维度颜色。而颜色的实现是可以独立变化的,和尺码、印花没有关联。一共有三种类型的颜色,红黄蓝。所以我们再建立一个继承体系,可以有这样的实现
桥接模式-组合-多维度.png
网友评论