设计模式拾荒之桥接模式: 可以变化的抽象类与接口

  1. 参考书籍: 《Design Patterns: Elements of Reusable Object-Oriented Software》
  2. Beidge pattern - Wiki
    桥接模式是一种提及频率极高, 但真正应用频率较少的设计模式。 桥接模式之所以被频繁提及, 是因为它的设计意图提到了“解耦”, 然而它的解耦方式却常常被很多人误解。

设计模式用前须知

  • 设计模式种一句出现频率非常高的话是,“ 在不改动。。。。的情况下, 实现。。。。的扩展“ 。
  • 对于设计模式的学习者来说,充分思考这句话其实非常重要, 因为这句往往只对框架/ 工具包的设计才有真正的意义。因为框架和工具包存在的意义,就是为了让其他的程序员予以利用, 进行功能的扩展,而这种功能的扩展必须以不需要改动框架和工具包中代码为前提
  • 对于应用程序的编写者, 从理论上来说, 所有的应用层级代码至少都是处于可编辑范围内的, 如果不细加考量, 就盲目使用较为复杂的设计模式, 反而会得不偿失, 毕竟灵活性的获得, 也是有代价的。

桥接模式 ( Bridge )

  • 设计意图
    • GOF: “ 解耦抽象与实现, 使得二者可以独立地变化。”
      • 80% 的程序员听到这句话, 第一反应都是: 一个接口(抽象类)可以对应多个实现类, 通过接口可以解耦抽象与实现。
      • 有趣的是, 第一反应是错误的。 原因是设计意图的后半句话没有被满足: “二者可以独立地变化
        • 当一个接口被定义好以后,实现类的确可以随意变化, 但是接口可以独立于实现类随意变化吗? 如果你向接口中添加一个方法, 那之前所有实现了该接口的类是否还满足该接口?
        • 答案显然是 “不”。
    • GOF举例 :
      • 考虑一个用于编写图形界面的工具包/类库, 假设该工具包支持编写可移植的跨平台界面, 其中定义了一个窗口的抽象(接口/抽象类)Window。 为了支持X平台和PM平台, 需要两个实现类 XWindow, PMWindow.
      • 到目前为止, 一切都很好。 可是当你试图想要扩展Window的抽象时, 就会发现问题。 假设我们要定义一个抽象类 IconWindow, 专用于描述 “图标窗口” 所需要实现的方法。 此时由于IconWindow也需要支持X平台和PM平台, 所以就需要再实现XIConWindow, PMIconWindow。
      • 从上述结构可以看出, 每增加一种抽象类abcWindow, 都需要额外实现两个平台的XabcWindow, PMabcWindow 。 这个问题的深层次原因是 : 接口和其实现类是存在耦合关系的, 每当你想要改变接口定义的时候,实现类必须也有相应的变化。
  • 解决方案

  • 图例说明

    • 图片中的空心三角箭头,代表着继承(extends)或实现(Implement)关系, 由继承者/实现者 指向 被继承者/被继承者。
    • 图片中的实心三角箭头且箭头末尾没有圆圈的, 代表着单一的引用关系, 但是被引用的对象也有可能被其他对象引用。
    • 图片中的实心三角箭头且箭头末尾有圆圈的, 代表着一对多的引用关系。
    • 图片中的虚线实心三角箭头, 代表着创建或者实例化的关系。
    • 图片中的末端有圆圈的虚线是一个对方法体内容用伪代码说明的关系
  • 图例分析

    • 注意点1:Window定义的方法是有方法体的, Window 中定义的绘制矩形 DrawRect 操作, 都是通过调用 impl 的方法实现的。 (这并不意味着 Window 只能是一个抽象类, Java8 是支持在接口中定义方法体的哟)
    • 注意点3: WindowImpl 也是一个抽象类( 接口), 并非一个具体的实现类
    • 注意点4: Window 的子抽象类(子接口)中定义的新方法,都是通过调用 WindowImpl 中的方法或调用 Window 中的方法间接定义的。
      • 这一点是最容易被忽略的: 如果 Window 的子接口中,直接增加一个新的方法 DrawCircle(), 搭在Window 和 WindowImpl 之间“桥” 就失效了。 桥右端的实现类中, 是无法间接地实现DrawCircle 这个新增的方法的。
      • 桥接模式的设计意图虽然是让 “抽象部分“和“实现部分“ 能够独立变化,但经过上述分析不难发现, 抽象接口的变化还是有所限制的, 并不能随意变化。
      • 下面这张图更加形象地描述了桥接模式中重定义的抽象类(RedefinedAbstraction)中的Composite Operations 对于基础抽象类中(Abstraction)中的Abstract Operations 的依赖关系
  • 桥接模式泛化结构图

桥接模式与抽象工厂

  • 上述的例子里, 提到了跨平台的问题, 回想一下可以发现, 抽象工厂模式中所举的例子, 出发点也是跨平台, 那两者这件是否有所关联? 答案是肯定的。
    • 还是以之前的例子进行说明, 注意到基础抽象类 Window 中持有了一个 WindowImp 的引用imp , 如果 Window 可以预先确定要支持的只有X平台和PM平台, 就可以在 Window 构造器中通过参数来指定实例化哪一种 WindowImpl.
public Window ( String type )
{
    switch(type){
        case "X"
            this.imp =  new XWindowImp();
            break;
        case "PW"
            this.imp =  new PMWindowImpl();
            break;
    }
}

如果Window中无法预先确定有哪些实现类, 想给用户提供扩展支持平台的机会, 那么就可以利用抽象工厂模式来实例化impl 对象

public Window ( AbstractFactory factory )
{
  this.impl = factory.createWindowImpl()
}

通过传入用户自定义的新的Factory实现类 , 就可以扩展Window所支持的平台。

  • 综上, 抽象工厂模式可以用来搭建或者配置桥接模式中的 “桥梁” 。
  • 抽象工厂模式的核心还在于解决 “一系列相互依赖产品” 的创建问题, 这里提到的impl对象只有一个, 好像一个抽象工厂只在创造一类产品, 不太符合抽象工厂的设计初衷。 那么可以考虑, 假如还有Button等组件 也要应用桥接模式, 需要持有 ButtonImpl等, 此时抽象工厂模式就恰如其分地解决了所有的这些平台相关的实现类的创建问题, Button, Window 中持有的实例一定是同一个平台的实现类, 不会被混用。

你可能感兴趣的:(设计模式)