zoukankan      html  css  js  c++  java
  • 面向对象设计模式纵横谈:Bridge 桥接模式(笔记记录)

       桥接模式是一个比较难理解的设计模式,设计和分析的时候也不容易把握,咱们听听“李建忠”老师是怎么来讲的。我们还是从演变的角度来说问题,一步一步的来把问题说清楚。先谈谈“抽象”和“实现”的关系。

    抽象与实现

    抽象不应该依赖于实现细节,实现细节应该依赖于抽象。这个东西很容易理解,抽象的东西是从众多的实例里面提取出来的精华,最原始,最稳定的东西,枝枝叶叶都已经去掉了。

    image

    就像建房子一样,我们一定要把地基打结实,如果地基天天改,上面的房子也很难建的起来,就算建起来也要拆了重新来建。

    再者说,如果抽象B由于固有的原因,本身并不稳定,也有可能变化,怎么办?其实这里就引出了另一个方面的变化,抽象B也在变化,大家要想到这一点,否则会有些糊涂。

    举例说明

    假如我们需要开发一个同时支持PC和手机端的坦克游戏,游戏在PC和手机上功能都一样,都有同样的类型,面临同样的功能需求变化,比如坦克可能有很多种不同的型号:T50,T75,T90……

    对于其中的坦克设计,我们可能很容易设计出来一个Tank的抽象基类,然后各种不同型号的Tank继承自该类;

    image

     这一步实现一点问题也没有,也符合开闭原则,继续往下看。

    另外的变化原因

    但是PC和手机上的图形绘制、声效、操作等实现完全不同……因此对于各种型号的坦克,都要提供各种不同平台上的坦克实现:

    image

    我们一般会设计成这样,但是这样看很怪,这样的设计会带来很多问题:有很多重复代码,类的结构过于复杂,难以维护,最致命的是引入任何新平台,比如在TV上的Tank游戏,都会让整个类层级结构复杂化。我们做软件,修改的时候,修改的越少越好,说明隔离的比较好。

    动机(Motivation)

    思考上述问题的症结:事实上由于Tank类型的固有逻辑,使得Tank类型具有了两个变化的维度——一个变化的维度为“平台的变化”,一个变化的维度为“型号的变化”。

    如何应对这种“多维度的变化”?如何利用面向对象技术来使得Tank类型可以轻松地沿着“平台”和“型号”两个方向变化,而不引入额外的复杂度?

    意图(Intent)

    将抽象部分与实现部分分离,使它们都可以独立地变化。

    ——《设计模式》GoF

    桥模式所讲的抽象和实现,和我刚开始讲的抽象和实现是不同的。其实抽象和实现两个都是抽象的部分。更确切的理解,应该是将一个事物中多个维度的变化分离。抽象部分是基于实现部分来实现的

    这里的抽象部分和实现部分与文章开头所说的抽象和实现不一样。

    可以这样理解,把一个维度抽象,另一个维度是实现的基础。例如坦克型号是一个抽象维度,Tank,T50,T75,T90;PC或者手机上具体实现的维度也是一个抽象,Imp,PCImp,MobileImp,两个维度都是抽象的,一方是上层抽象,一方是下层抽象。

    举例说明Bridge应用,我们演变的来看,一般的实现逻辑是如下的:

    版本一 

    image

    image

    先写各种不同的坦克型号类T50、T75等继承自Tank,然后再让各种平台的坦克继承自对应型号的类,如PCT50,PCT75继承自T50等。这样设计可能会有很多重复的代码,例PCT50和PCT75。

    有了上面的分析,下面来看看我们的【模式版】的实现,您会惊叹的:

    image

    因为平台坦克型号都是变化,所以我们把平台的变化作为字段放到抽象类中。

    image

    image

    image

    平台实现类

    image

    下面是整个代码的骨架

    image

    Tank的型号,和Tank的平台都继承自各自的抽象类,因此它们的变化都不会影响到对方。而它们之间的关联,我们使用组合的方式,把平台类放到Tank类中作为属性。这再次体现了组合优先于继承的思想。多继承的方法就是版本一的代码,这种方式子类和父类的关系太紧,造成紧耦合。

    如何使用

    image

    在环境交互中使用的都是抽象类,并且把平台实现隐藏,在应用程序中new平台的方式也可以根据情况用Singleton模式或者Abstract Factory模式等实现。

    结构(Structure)

    image

    其中imp的地方就是一个组合。Abstraction就是我们之前例子中的Tank,它的子类RefinedAbstraction就是T50等型号。Implementor是TankPlatformImplementation类,ConcreteImplementorA和ConcreteImplementorB分别是PCTankImplementation和MobileTankImplementation。

    整个设计模式的关键就是组合的使用。

    Bridge模式的几个要点

    Bridge模式使用“对象间的组合关系”解耦了抽象和实现之间固有的绑定关系,使得抽象(Tank的型号)和实现(不同的平台)可以沿着各自的维度来变化。

    所谓抽象和实现沿着各自维度的变化,即“子类化”它们(比如不同的Tank型号子类,和不同的平台子类),得到各个子类之后,便可以任意组合它们,从而获得不同平台上的不同型号。

    Bridge模式有时候类似于多继承方案,但是多继承方案往往违背单一职责原则(即一个类只有一个变化的原因),复用性比较差。Bridge模式是比多继承方案更好的解决方法。

    下面是针对上面的例子,多继承接口的一种写法: 

    image

    这样PCT50既需要写T50的实现,又要写Platform的实现,它把型号和平台的变化都引入了PCT50。这样就把两个本不该扭在一起的事务扭在了一起,这样的设计更加糟糕,而且也违背了类的单一职责原则。

    Bridge模式的应用一般在“两个非常强的变化维度”,有时候即使有两个变化的维度,但是某个方向的变化维度并不剧烈——换言之两个变化不会导致纵横交错的结果,并不一定要使用Bridge模式。

    桥模式并不同于适配器模式,适配器模式其实是一个事后诸葛亮,当发现以前的东西不适用了才去做一个弥补的措施。桥模式相对来说所做的改变比适配器模式早,它可以适用于有两个甚至两个以上维度的变化。

  • 相关阅读:
    part11-1 Python图形界面编程(Python GUI库介绍、Tkinter 组件介绍、布局管理器、事件处理)
    part10-3 Python常见模块(正则表达式)
    Cyclic Nacklace HDU
    模拟题 Right turn SCU
    状态DP Doing Homework HDU
    Dp Milking Time POJ
    区间DP Treats for the Cows POJ
    DP Help Jimmy POJ
    Dales and Hills Gym
    Kids and Prizes Gym
  • 原文地址:https://www.cnblogs.com/PatrickLiu/p/6973136.html
Copyright © 2011-2022 走看看