`
langgufu
  • 浏览: 2305568 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

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. AbstractionImplementor的聚合关系。该关系可以由RefinedAbstractionImplementor的聚合关系代替。理由是Java编程的时候Abstraction可能会实现为interface,不能与Implementor构成聚合关系。

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

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

二. 解决的问题

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

三. 一个更加复杂的例子

这里我们给出一个更为复杂的例子,并且用一种循序渐进的方式描述,逐渐加入新的功能和约束条件。设想我们要做一个编辑器(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接口又使用了组合这其实是联合inheritancecomposition的优点。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();
    }
}
输出仍然没有变化。(前面做的其实也可以算作是重构吧,可观察行为没有变化)
不得不承认,现在客户端又需要了解什么样的文件用什么编辑器打开了。但是一个小的变化就可以避免这一点。为FileEditable增加一个不包含Editor参数的构造函数。
    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


虽然我前面已经说了,
AbstractionImplementor的聚合关系可以由派生类RefinedAbstractionImplementor的聚合关系代替,为了与经典模式类图保持尽量的相似,以便大家容易理解,我们还是在StringEditableFileEditable上面加了一层: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的区别》个人认为,作者在BridgeStrategy模式的区别上面有误解。下面有我的回复:

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

继续:)

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

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

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

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

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

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

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

设计模式速查,很不错

分享到:
评论

相关推荐

    (结构型模式)Bridge模式

    Bridge模式是一种设计模式,属于结构型模式之一,其主要目的是将抽象部分与实现部分分离,使得两者可以独立地进行变化。这种模式的核心思想是“抽象不应该依赖于具体,而应该依赖于抽象”。Bridge模式通过引入一个...

    【Java 设计模式-源码】Bridge 模式:在 Java 中解耦抽象与实现

    本文将介绍 Bridge 模式,它是一种结构型设计模式,用于解耦抽象与实现,增强软件系统的灵活性和可扩展性。 二、Bridge 设计模式的别名 Handle/Body 三、Bridge 设计模式的意图 Bridge 设计模式是 Java 中的一种结构...

    Docker基础-19-网络-bridge模式和docker0详解.rar

    本资料主要探讨了Docker的网络桥接模式(Bridge模式)以及其默认网络设备docker0,这对于理解和运用Docker服务至关重要。 首先,Docker的Bridge模式是Docker容器默认的网络模式。在这个模式下,每个容器都会被分配...

    Bridge模式代码

    Bridge模式是一种设计模式,主要目的是将抽象部分与实现部分解耦,使得它们可以独立地进行变化。在Java编程中,这种模式的应用可以帮助我们构建更灵活、可扩展的系统。Bridge模式通常由四个主要角色组成:抽象...

    设计模式之Bridge模式

    Bridge模式是对抽象和具体的进一步抽象。假设你有一个抽象类,一个具体类,现在需要将两者的变化都考虑进去,这就需要bridge。这是经典设计模式中唯一的可以同时处理抽象和具体同时变化的设计模式

    Docker为网络bridge模式指定容器ip的方法

    Docker在创建容器时有四种网络模式,bridge为默认不需要用--net去指定,其他...那Docker为网络bridge模式指定容器ip该如何实现呢?下面通过通过这篇文章一起看看吧,文中给出了详细的示例代码,有需要的可以参考借鉴。

    Java设计模式之桥模式(Bridge模式)介绍

    桥模式(Bridge模式)是一种设计模式,主要用于将抽象部分与实现部分解耦,使得它们能够独立地变化。在Java中,这种模式可以帮助我们构建更加灵活和可扩展的系统。以下是关于桥模式的详细解释: 首先,理解桥模式的...

    软件设计模式 bridge模式 代理模式等

    软件设计模式,共包含二十多种模式,做IT的必备知识呀!

    bridge结构设计模式

    在给定的文件列表中,我们可以看到涉及到Bridge模式的一些关键组件: 1. **Abstraction**(抽象类):这是桥接模式的核心抽象部分,定义了客户需要的接口,并持有一个指向实现部分的指针。在`abstraction.cpp`和`...

    设计模式探索系列之Bridge模式

    这就要使用Bridge模式。 意图 将抽象部分与实现部分分离,使它们都可以独立的变化。[GOF《设计模式》] 结构图图1Bridge模式结构图 生活中的例子 桥接模式将抽象部分与它的实现分离,使它们能够独立地变化。一个...

    23种设计模式的java实现—Bridge

    《Java实现的23种设计模式——Bridge模式详解》 设计模式是软件工程中的宝贵经验总结,它们为解决常见问题提供了可复用的解决方案。在众多设计模式中,Bridge模式是一种结构型模式,旨在将抽象部分与其实现部分分离...

    java设计模式之Bridge.docx

    Java设计模式中的Bridge模式是一种结构型设计模式,其主要目的是将抽象部分与其实现部分分离,使得两者可以独立地进行变化。在Bridge模式中,抽象类(或接口)不直接包含实现,而是通过一个引用(或接口)指向实现类...

    java设计模式之Bridge.pdf

    在上述例子中,Bridge模式被用来解决咖啡种类的问题。咖啡可以按照杯型(中杯、大杯)和是否加奶(加奶、不加奶)来分类。如果简单地使用继承,会形成一个四层的类结构,这将导致类的复杂性和扩展困难。Bridge模式...

    移动公司 吉比特 光猫 ZXHN G6611 路由模式router 改 桥接模式bridge 方法 详细教程

    我们需要选择Bridge模式,并启用DHCP使能。这将允许电脑连接光猫时可以自动获得IP地址。然后,我们需要访问光猫的配置页面,并勾选业务模式为INTERNET,vlan模式改为TAG,vlanid为200,802.1p优先级为6。 步骤3:...

    23种设计模式整理pdf

    Bridge 模式是一种结构型模式,用于提供一种分离对象的接口和实现的接口。Bridge 模式的优点是可以提供一种统一的分离对象的接口和实现的接口,减少了代码的耦合度。Bridge 模式的缺点是它可能会增加代码的复杂度。 ...

    ACE资料系列 - ACE中设计模式

    Bridge 模式是一种结构型设计模式,主要用于将抽象部分与实现部分分离,使得它们可以独立地变化。它可以使得抽象部分和实现部分之间的绑定变得松散,从而提高了系统的灵活性和可维护性。 2. ACE 框架中的 Bridge ...

Global site tag (gtag.js) - Google Analytics