<转>对Bridge模式的理解

Bridge模式的理解

摘要:本文首先解释了Bridge模式的定义。然后通过一个例子,一步步将Bridge模式实现。

在一切开始之前,请允许我先给出三条经典名言:Design to interfaces. Find what varies and encapsulate it. Favor composition over inheritance.后面我们会反复,并且是反反复复的用到。我认为在做设计的时候这三句话要牢牢的印在脑子里。

一.  定义

根据GOF的定义,Bridge模式的目的是“解耦抽象与它的实现,以便二者可以独立的变化。”这个定义中最容易误解的“抽象”与“它的实现”。因为这两个词在面向对象的语言中都有对应的关键字。在Java中即是“abstract”和“implement”,所以很容易造成困惑的是认为要解耦一个抽象类和它的实现类。实际上,这里实现是指的抽象类和它的派生类用以实现自己的对象,进一步说就是这里的抽象指的是一个概念或者说是一个继承体系中的对象,而实现抽象使用并完成自己的功能。举个例子,这是吕震宇兄想出的一个例子(http://www.cnblogs.com/zhenyulu/articles/67016.html),这是我见过的最经典的例子了,以至于我不得不在这里重复一遍:

小时候我们都用蜡笔画画,一盒蜡笔12种颜色。一开始我都是用最小号的蜡笔画个太阳公公、月亮婆婆足够了。后来开始画一些抽象派的作品,就得换中号的了,要不然画个背景都要描半天,好一盒中号的也是12种颜色。再后来我开始转向豪放派,中号就有些捉襟见肘了,只好换大号的了,好一盒大号的也只有12种颜色。你看,像我这样不太出名的画家就需要36种画笔,哇,太麻烦了。但是据我观察,另一些比我出名的画家倒是没有这么多笔,他们只有几把刷子和一些颜料,这样就解决了蜡笔的“种类爆炸”问题。如下图所示,注意图也是从吕兄的网站偷来的。


我要用36种蜡笔

齐白石老先生只用3种毛笔和12种颜料

回到上面的定义,定义里面的抽象指的就是毛笔,实现就是颜料。颜料被毛笔使用,用以完成毛笔的功能。


(http://www.niufish.com/books/Pattern/com/niufish/pattern/bridge/package-use.html)

在下认为上图中有以下几点可以变通:

1.   Abstraction与Implementor的聚合关系。该关系可以由RefinedAbstraction和Implementor的聚合关系代替。理由是Java编程的时候Abstraction可能会实现为interface,不能与Implementor构成聚合关系。

2.   Operation方法不一定在Abstraction中做实现,理由同上,但是必须声明。

可见在Bridge模式中有两个继承体系,为了方便描述我们称左边的为Abstraction继承体系,右边为Implementor继承体系。Abstraction使用Implementor完成自己的功能。同时,该模式允许Abstraction和Implementor各自独立变化(所谓变化,我认为就是派生)。

二.  解决的问题

上面已经说了,提出毛笔的概念目的是解决蜡笔的“种类爆炸”问题。3×12变成了3+12,并且在将来毛笔型号和颜料种类可以独立的扩充。上面的例子在说明这种设计模式的特点和优势上很有好处,但是毕竟我们实际编码中很少这么幸运的碰上这么简单的问题。下面我用一个相对复杂的问题来重新描述这个模式,关于蜡笔和毛笔的故事的代码可以到上面给出的链接查找。

三.  一个更加复杂的例子

这里我们给出一个更为复杂的例子,并且用一种循序渐进的方式描述,逐渐加入新的功能和约束条件。设想我们要做一个编辑器(Editor),可以打开文本文件,但是不同的文件要求用不同的编辑器打开。比如“.txt”文件用文本编辑器打开,而“.xml”文件用xml编辑器打开。


代码如下:

Editor接口

package com.gemplus.editor;

public abstract interface Editor {

public void openFile(String path);

}

TextEditor实现

package com.gemplus.editor;
public class TextEditor implements Editor {
    public void openFile(String path) {
        System.out.println("Open file with Text Editor. FileName: " + path);
    }
}

XMLEditor实现

package com.gemplus.editor;
public class XMLEditor implements Editor {
    public void openFile(String path) {
        System.out.println("Open file with XML Editor. FileName: " + path);
    }
}

Client

package com.gemplus.editor;
public class Client {
    public static void main(String[] args) {
        Editor xmlEditor = new XMLEditor();
        xmlEditor.openFile("test.xml");
        Editor textEditor = new TextEditor();
        textEditor.openFile("test.txt");
    }
}

输出

Open file with XMLEditor. FileName: test.xml

Open file with Text Editor. FileName: test.txt

到目前为止,我们什么模式也没用到。不过我们还是用到了一项伟大的技术多态,还有就是对接口编程。代码写的还算优雅,只是没有写注释。

接下来我们要增加一个功能,与其说是功能,不如说是一项约束。就是,我不希望客户端了解那些文件要由xmlEditor打开,哪些由textEditor打开。但是总要有人知道,对吧?这项看似直观的约束往往被忽略,或者在潜意识中没有意识到。为了简单起见,我们引入一个简单工厂EditorImpl。

public class SmartEditor implements Editor {
    private static Editor textEditor = new TextEditor();
    private static Editor xmlEditor = new XMLEditor();
    
    public void openFile(String path) {
        if (path.endsWith(".xml")) {
            xmlEditor.openFile(path);
        } else {
            textEditor.openFile(path);
        }
    }
}

Client也要做相应修改

public class Client {
    public static void main(String[] args) {
//        Editor xmlEditor = new XMLEditor();
//        xmlEditor.openFile("FileName");
//        Editor textEditor = new TextEditor();
//        textEditor.openFile("FileName2");
        Editor editor = new SmartEditor();
        editor.openFile("test.xml");
        editor.openFile("test.txt");
    }
}

输出结果与修改之前完全一样,但是现在Client端已经不需要知道应该使用哪个Editor了,这其实也是对Find what vary and encapsulate it的应用。另外一点,也可以请大家注意在EditorImpl的实现中,即继承了Editor接口又使用了组合这其实是联合inheritance和composition的优点。Javaworld上有一篇文章(作者Bill Venners)就是讲这个技巧和使用方法的,我找了半天没找到,以后找到再把链接添上。

先别高兴太早,我们看看目前这种设计有什么问题。SmartEditor在充满了技巧和高级的、伟大的技术的同时,你有没有觉得它管的东西太多了呢?一个Editor要去关心文件名的问题,不错SmartEditor出现的目的就是要来关心文件名,然而从概念上讲,仍然不是好的设计。我们来看看什么在变化?答案是文件类型。OK,封装之!

我们把它称作什么呢?对应与Edtior,我们不妨称之为Editable。(说实话,我这样这个概念建立起来有一些生硬,但是你可以想像一下:如果被编辑的东西是更加复杂的呢?我所取的例子实际上是我工作中的一个例子,要编辑的东西要比这里描述的复杂的多!)


简单之极,以至于我觉得没有必要给出代码。但是第一次写文章,总要给大家留点好印象。

public class FileEditable implements Editable {
    Editor editor;
    String filePath;

 

    public FileEditable(Editor editor, String filePath) {
        this.editor = editor;
        this.filePath = filePath;
    }
    public void open() {
        editor.openFile(filePath);
    }
}

Client端

public class Client {
    public static void main(String[] args) {
        Editable editableText = new FileEditable(new TextEditor(), "test.txt");
        Editable editableXML = new FileEditable(new XMLEditor(), "test.xml");
        editableText.open();
        editableXML.open();
    }
}
FileEditableEditor
    public FileEditable(String filePath) {
        this.filePath = filePath;
        if (filePath.endsWith(".xml")) {
            editor = new XMLEditor();
        } else {
            editor = new TextEditor();
        }
    }

现在该是增加功能的时候了。现在我们要求,用户不但可以打开文件也有可能用这个Editor来编辑一段文字,即字符串。我们假设,所有的Editor都可以编辑文件和字符串。我们为Editor增加一个方法:openString。前面我们说了,Bridge模式中有两个继承体系,两边可以独立变化。现在我们有了两个Editable,即两种可编辑体:文件和字符串。也可以看出我们当初把Editable封装起来多么的英明神武啊!!

在Editable的继承体系中又增加了一员:StringEditable。


虽然我前面已经说了,Abstraction和Implementor的聚合关系可以由派生类RefinedAbstraction和Implementor的聚合关系代替,为了与经典模式类图保持尽量的相似,以便大家容易理解,我们还是在StringEditable和FileEditable上面加了一层:EditableImpl。


好了,我们已经实现了Bridge模式。不过这里面还是有一点让人不舒服的地方,就是Editor中包含的两个方法,实际上对应了两种Editable。也许是这个例子举的不好。但是《设计模式 explained》中的例子和本例大同小异。

我也是刚刚开始学习设计模式,如果有什么不对的地方请大家指出。接下来,我会继续这个例子,增加一个更加复杂的功能:可以嵌套组合的Editor。比如对于同一个文件我们可能希望有两种打开方式,以网页为例,我们希望在一个Editor中包含两个子Editor,分别显式源代码和页面效果。这里面会用到Composite模式。

参考资料:

1.   http://www.cnblogs.com/zhenyulu/articles/67016.html

《设计模式随笔-蜡笔与毛笔的故事》本文对于Bridge模式给了一个相当漂亮的比喻,并且给出了代码示例。

2.   http://www.cnblogs.com/zhenyulu/articles/62720.html

《设计模式(16)-Bridge Pattern》这篇文章没有仔细看,前面的角色定义和解释很精辟。

3.   http://www.cnblogs.com/idior/articles/97283.html

《Bridge Strategy 和State的区别》个人认为,作者在Bridge和Strategy模式的区别上面有误解。下面有我的回复:

我倒是不赞同楼主所说在“蜡笔与毛笔的故事”中“缺少被抽象的行为”的说法。Bridge模式中解耦的是“抽象”与“实现”。这里的实现不一定是对“行为”的抽象,按照《Design Pattern Explained》 一书中所说,这里实现是指的抽象类和它的派生类用以实现自己的某个对象(本身是抽象与其派生类构成的继承体系,要不怎么变化呢)。进一步说就是这里的抽象 指的是一个概念或者说是一个继承体系中的对象,而实现则是被抽象使用并完成自己的功能的另一个继承体系。桥梁模式的目的就是让这两个继承体系可以,独立的 变化、派生。蜡笔与毛笔的故事中毛笔(第一个继承体系)和颜料(第二个继承体系)可以独立的变化,所以我认为这是个非常恰当的桥梁模式的例子。

继续:)

我觉得区分两个模式的方法不是从模式的实现上面看,因为一个模式的实现往往夹杂了其它的模式,比如idior给的第一个例子中就有Template Method模式。我赞成吕震宇的说法,这里面有Bridge模式的影子,甚至我觉得不止是影子,这本身是一个Bridge模式的例子。

区分两个模式的方法应该从解决的问题上看,也就是从context上分析。

我觉得简单的说Strategy模式是从N变化为1+N,原来有N个类但是这N个类里面只有某个算法的区别,我们把N个算法提取出来就变成了1个抽象类(不要理解成Java中的abstract class,而是这个抽象类表示一个概念)和N个实现类(同理,不要理解成对前面那个抽象类的实现,而是辅助实现抽象类的某个功能的一个继承体系)。注意这里只有一个继承体系。

而Bridge模式是从M×N变化为M+N,原来系统中有M×N个类,但是从中可以提取出N个算法(或者辅助类)和M个主体(我想不出一个好的名次)。这样构成了两个继承体系,N个算法(颜料)构成一个继承体系,M个主体类(毛笔的不同型号)构成一个继承体系。两个继承体系可以独立的变化。

从解决的问题上看,二者都要解决重复代码的问题,但是前者不强调锥把(见吕震宇的回复)的变化,而后者强调,并且强调锥头和所有锥把的兼容。我认为这才是二者的根本区别。

这是我个人的理解,与楼主商榷。

4.    http://www.niufish.com/books/Pattern/com/niufish/pattern/bridge/package-use.html

设计模式速查,很不错

对Bridge模式的理解相关推荐

  1. 伤脑筋的bridge模式-我不明白。。

    看了看 看了看..看不明白. 还是那图,还是那人. 伤脑筋. 不过看到了几句话: oo的原则 之一就是 相比使用继承 使用 对象组合更好 . 另外: GoF 在说明Bridge 模式时,在意图中指出B ...

  2. 设计模式学习笔记——桥接(Bridge)模式

    设计模式学习笔记--桥接(Bridge)模式 @(设计模式)[设计模式, 桥接模式, bridge] 设计模式学习笔记桥接Bridge模式 基本介绍 类的功能层次结构 类的实现层次结构 使用桥接模式的 ...

  3. java bridge 模式_学习:java设计模式—Bridge模式

    一.引子 下面是吕振宇大牛的一个例子,个人觉得挺好的,有助于理解Bridge模式的设计目的: 设想要绘制一幅图画,蓝天.白云.绿树.小鸟,如果画面尺寸很大,那么用蜡笔绘制就会遇到点麻烦.毕竟细细的蜡笔 ...

  4. Docker中的bridge模式,可以这么设置

    最近有几个已经就业的小伙伴,过来问千锋健哥关于Docker网络配置的问题,他们在实际开发中还是有些疑问.关于Docker网络这一块的内容确实很多,为了让大家搞清楚这个问题,健哥准备搞几篇系列文章,来为 ...

  5. 图解设计模式-Bridge模式

    Bridge桥连接模式的作用是将两样东西链接起来,它们分别是类的功能层次结构和类的实现层次结构. 类的功能层次结构 当有一个Something类,要增加它的功能时,可以创建SomethingGood类 ...

  6. Docker学习:容器五种(3+2)网络模式 | bridge模式 | host模式 | none模式 | container 模式 | 自定义网络模式详解

    前言 本讲是从Docker系列讲解课程,单独抽离出来的一个小节,重点介绍容器网络模式, 属于了解范畴,充分了容器的网络模式,更有助于更好的理解Docker的容器之间的访问逻辑. 疑问:为什么要了解容器 ...

  7. 结构型模式之Bridge模式

    1.意图 将抽象部分与实现部分分离,使它们都可以独立地变化 2.适用性 以下一些情况使用Bridge模式 (1)你不希望在抽象和它的实现部分之间有一个固定的绑定关系.例如这种情况可能是因为在程序运行时 ...

  8. 设计模式--桥(Bridge)模式

    模式定义 将抽象部分(业务功能)与实现部分(平台实现)分离,使它们都可以独立地变化. 类图 应用场景 在业务功能具有抽象功能和差异实现时需要独立的适应后面可能遇到的变化时使用桥接模式 优点 1.符合开 ...

  9. MVC架构中的Repository模式 个人理解

    关于MVC架构中的Repository模式 个人理解:Repository是一个独立的层,介于领域层与数据映射层(数据访问层)之间.它的存在让领域层感觉不到数据访问层的存在,它提供一个类似集合的接口提 ...

最新文章

  1. Foundation HTML5 Canvas中的2处错误
  2. 硬件安全(二) 5G时代IOT环境下芯片安全风险与挑战
  3. UVa 1629 切蛋糕(记忆化搜索)
  4. JAVA读取文件操作时路径的斜杠问题
  5. 周思进:产品和服务在“骂”与“被骂”中不断打磨
  6. Python 内置模块之 logging
  7. 阿里云 x 蒙牛 | 打通数据孤岛,基于MaxCompute实现产销协同的智慧运营
  8. linux git文件图标,Linux下使用git管理项目
  9. 多线程Socket传送文件的客户端和服务端源代码
  10. JavaWeb学习心得之自定义传统标签
  11. java需要数学_学java要数学好嘛?
  12. 计算机行业的最新技术,计算机行业发展空间巨大 三大必然趋势引领发展
  13. Unity 3D 网络游戏架构设计
  14. 概率论与数理统计——卡方分布的期望与方差
  15. python高德地图api调用实例_Python调用高德地图API实现经纬度换算、地图可视化
  16. TCP连接大量CLOSE_WAIT状态问题排查
  17. 【其他】手机bilibili的视频文件在哪个目录
  18. 如何比较两个结构体是否相等
  19. IC卡解密从零开始学2 版本更新! 解密工具PN532-mfoc-mfcuk-GUI V2.1 By:lookyour
  20. TF-IDF文本表示方法与词云图

热门文章

  1. 包装exp是什么意思_全包装修是什么意思?全包6万装修60平米的房子好不好?-广州亚运城装修...
  2. 《系统集成项目管理工程师》必背100个知识点-70合同索赔流程
  3. 笔记-信息系统开发基础-信息系统生命周期
  4. Vue中使用uuidv1根据时间戳和MAC地址生成唯一标识
  5. Winform中实现连接Mysql并获取所有表名
  6. Angular中使用双向数据绑定操作表单的input、checkboc、radio、select、textarea实现简单的人员登记系统实例
  7. MyBatisPlus中使用 @TableField完成字段自动填充
  8. IT项目管理总结:第二章 项目管理和IT背景
  9. 关于CMMI级别阶梯式前进路线图的对话
  10. python数据的格式输出_python数据类型,格式话输出