浅析设计模式-桥接模式

作者: RunAlgorithm | 来源:发表于2017-08-21 23:48 被阅读158次

    定义

    桥接模式(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

    相关文章

      网友评论

        本文标题:浅析设计模式-桥接模式

        本文链接:https://www.haomeiwen.com/subject/dmckdxtx.html