桥接模式——多维度的组合


Demo 地址: https://github.com/ooblee/HelloDesignPattern

1. 定义

桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。

就是用来解决多维度变化的问题

拿T恤举例,T恤根据印花的分类,有局部印花,全身印花,刺绣,然后又有不同的尺码,每种类型都有 S,M,L。所以T恤有了两个维度的变化,一个维度印花,一个维度尺码

如果我们用多重继承的方式来实现T恤,我们会先继承T恤,生成三种印花T,然后每种印花T又各自派生三种尺码的T

继承实现

关于制定尺寸是有一套统一的规则的,和 T 恤印花类型并没有的关联,多重继承的实现,每个类都要走一遍不同 size 的实现,会出现代码冗余。而且实现所有款式和尺码的衣服,一共需要1+3+3*3=13个类。而且一旦每增加一个尺码,每种印花类型又要派生多个子类,类的数量增长很快

现在我们用桥接模式来改造它,把这两个维度拆开,独立两套继承体系,然后在使用的时候组合在一起。首先以 T 恤的印花为主,一个继承体系,有三种子类。然后尺码在T恤类中是抽象类型的,不涉及具体的实现

组合实现

而具体的实现由程序运行时动态指定。于是我们有 1+3+1+3=8个类,就可以实现所有尺寸的 T 恤。

同时,把这两个维度剥离开,形成两套继承体系单独维护。把类之间的静态继承关系,转化为动态组合关系。两个维度单独变化,两个维度的关联采用抽象的方式连接,动态去实现,降低了耦合,同时容易扩展

这样,两个单独的继承体系通过某个抽象结构连接在一起,就称为桥接。是组合优于继承的典型模式

2. 设计

主要角色

  • 抽象类 ,抽象业务,维持对实现类接口的一个引用。

  • **抽象实现类 **,实现具体业务。

  • 实现类接口,定义实现类的接口。

  • 具体实现类,实现接口。

简单的类图如下

桥接模式-类图

运行时,为 Abstraction 的具体实例,动态组合 Implemetor 的具体实例,两者桥接在一起

这两个维度单独变化,对任何一个维度的扩展都不会干扰到另一个维度

3. 应用

在这里场景下可以应用桥接模式:

  • 系统希望抽象部分和实现部分独立扩展。

  • 类存在多个变化的维度,每个维度有独立的继承体系。

  • 继承或者多重继承导致类数量膨胀。

3.1. 菜单

假设我们有这样一个需求,在一个页面有三个菜单,菜单 A,菜单 B 和 菜单 C。菜单 A 和 菜单 B 有不同的 UI。菜单 C 只比 菜单 B 多了一个菜单选项,其他都一样

每个菜单都有多个菜单选项,菜单中会有相同的菜单选项。菜单 A 有点赞、举报;菜单 B 有举报、删除;菜单 C 有点赞、举报、删除

菜单选项有这几种,点赞、举报、删除

菜单 B 还会随着服务端返回的数据有变化,比如根据数据增加一个点赞的选项。每个菜单选项都会执行相应的业务

后面产品需求的变化,各个菜单的菜单选项可能发生增减,而每种菜单项内部的实现也可能发生变化

如果采用多重继承,没增加或者减少一个菜单项,都当成一个新类型的菜单,这样静态继承创建菜单的方式,会让类数量变多。而任何一个菜单选项实现的改变,又会影响拥有该类型选项的菜单代码。维护起来很艰难

现在我们考虑用桥接模式来改造它

总结以上需求,我们这里有两个维度的变化:菜单UI+菜单项。UI 只有两种,A 风格和 B风格。菜单项有三种,点赞、举报、删除。那么就以菜单 UI 这个维度为菜单抽象类的基础,形成一个菜单继承体系;菜单项单独出去形成另一个维度。使用的时候,再去动态设置菜单选项

菜单类图

在使用菜单的地方,动态地菜添加菜单项的实现

// 创建 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 风格菜单。然后菜单项同样动态组合进来

多用组合,少用继承,代码可以变得灵活。以后需求变化了,也可以减少改动量

3.2. 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

4. 特点

4.1. 优势

  • 组合实现。抽象和实现分离,独立变化,使用的使用可以动态组合。
  • 优化多重继承。减少类的数量。
  • 易扩展。两个维度单独变化,修改和扩展互不影响。

###4.2. 缺点

  • 难理解。
  • 使用需要经验。

5. 扩展思考

这里只处理了两个维度,如果有多维度变化怎么办?

那就再加个实现,然后与当前抽象桥接在一起

还是拿 T 恤举例,这时候,我们又多了一个新的维度颜色。而颜色的实现是可以独立变化的,和尺码、印花没有关联。一共有三种类型的颜色,红黄蓝。所以我们再建立一个继承体系,可以有这样的实现

多维度的组合

发布了61 篇原创文章 · 获赞 43 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/firefile/article/details/90313997
今日推荐