首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 软件管理 > 软件开发 >

研磨设计方式之桥接模式-3

2012-09-15 
研磨设计模式之桥接模式-3?3? 模式讲解 3.1? 认识桥接模式(1)什么是桥接??????? 在桥接模式里面,不太好理

研磨设计模式之桥接模式-3

?

3? 模式讲解 3.1? 认识桥接模式

(1)什么是桥接
??????? 在桥接模式里面,不太好理解的就是桥接的概念,什么是桥接?为何需要桥接?如何桥接?把这些问题搞清楚了,也就基本明白桥接的含义了。
??????? 一个一个来,先看什么是桥接?所谓桥接,通俗点说就是在不同的东西之间搭一个桥,让他们能够连接起来,可以相互通讯和使用。那么在桥接模式中到底是给什么东西来搭桥呢?就是为被分离了的抽象部分和实现部分来搭桥,比如前面示例中抽象的消息和具体消息发送之间搭个桥。
??????? 但是这里要注意一个问题:在桥接模式中的桥接是单向的,也就是只能是抽象部分的对象去使用具体实现部分的对象,而不能反过来,也就是个单向桥。


(2)为何需要桥接
??????? 为了达到让抽象部分和实现部分都可以独立变化的目的,在桥接模式中,是把抽象部分和实现部分分离开来的,虽然从程序结构上是分开了,但是在抽象部分实现的时候,还是需要使用具体的实现的,这可怎么办呢?抽象部分如何才能调用到具体实现部分的功能呢?很简单,搭个桥不就可以了,搭个桥,让抽象部分通过这个桥就可以调用到实现部分的功能了,因此需要桥接。

(3)如何桥接
??????? 这个理解上也很简单,只要让抽象部分拥有实现部分的接口对象,这就桥接上了,在抽象部分就可以通过这个接口来调用具体实现部分的功能。也就是说,桥接在程序上就体现成了在抽象部分拥有实现部分的接口对象,维护桥接就是维护这个关系。


(4)独立变化
??????? 桥接模式的意图:使得抽象和实现可以独立变化,都可以分别扩充。也就是说抽象部分和实现部分是一种非常松散的关系,从某个角度来讲,抽象部分和实现部分是可以完全分开的,独立的,抽象部分不过是一个使用实现部分对外接口的程序罢了。
??????? 如果这么看桥接模式的话,就类似于策略模式了,抽象部分需要根据某个策略,来选择真实的实现,也就是说桥接模式的抽象部分相当于策略模式的上下文。更原始的就直接类似于面向接口编程,通过接口分离的两个部分而已。但是别忘了,桥接模式的抽象部分,是可以继续扩展和变化的,而策略模式只有上下文,是不存在所谓抽象部分的。
??????? 那抽象和实现为何还要组合在一起呢?原因是在抽象部分和实现部分还是存在内部联系的,抽象部分的实现通常是需要调用实现部分的功能来实现的。


(5)动态变换功能
??????? 由于桥接模式中的抽象部分和实现部分是完全分离的,因此可以在运行时动态组合具体的真实实现,从而达到动态变换功能的目的。
??????? 从另外一个角度看,抽象部分和实现部分没有固定的绑定关系了,因此同一个真实实现可以被不同的抽象对象使用,反过来,同一个抽象也可以有多个不同的实现。就像前面示例的那样,比如:站内短消息的实现功能,可以被普通消息、加急消息或是特急消息等不同的消息对象使用;反过来,某个消息具体的发送方式,可以是站内短消息,或者是Email,也可以是手机短消息等具体的发送方式。

(6)退化的桥接模式
??????? 如果Implementor仅有一个实现,那么就没有必要创建Implementor接口了,这是一种桥接模式退化的情况。这个时候Abstraction和Implementor是一对一的关系,虽然如此,也还是要保持它们的分离状态,这样的话,它们才不会相互影响,才可以分别扩展。
??????? 也就是说,就算不要Implementor接口了,也要保持Abstraction和Implementor是分离的,模式的分离机制仍然是非常有用的。

(7)桥接模式和继承
??????? 继承是扩展对象功能的一种常见手段,通常情况下,继承扩展的功能变化纬度都是一纬的,也就是变化的因素只有一类。
??????? 对于出现变化因素有两类的,也就是有两个变化纬度的情况,继承实现就会比较痛苦。比如上面的示例,就有两个变化纬度,一个是消息的类别,不同的消息类别处理不同;另外一个是消息的发送方式。
??????? 从理论上来说,如果用继承的方式来实现这种有两个变化纬度的情况,最后实际的实现类应该是两个纬度上可变数量的乘积那么多个。比如上面的示例,在消息类别的纬度上,目前的可变数量是3个,普通消息、加急消息和特急消息;在消息发送方式的纬度上,目前的可变数量也是3个,站内短消息、Email和手机短消息。这种情况下,如果要实现全的话,那么需要的实现类应该是:3 X 3 = 9个。
??????? 如果要在任何一个纬度上进行扩展,都需要实现另外一个纬度上的可变数量那么多个实现类,这也是为何会感到扩展起来很困难。而且随着程序规模的加大,会越来越难以扩展和维护。
??????? 而桥接模式就是用来解决这种有两个变化纬度的情况下,如何灵活的扩展功能的一个很好的方案。其实,桥接模式主要是把继承改成了使用对象组合,从而把两个纬度分开,让每一个纬度单独去变化,最后通过对象组合的方式,把两个纬度组合起来,每一种组合的方式就相当于原来继承中的一种实现,这样就有效的减少了实际实现的类的个数。
??????? 从理论上来说,如果用桥接模式的方式来实现这种有两个变化纬度的情况,最后实际的实现类应该是两个纬度上可变数量的和那么多个。同样是上面那个示例,使用桥接模式来实现,实现全的话,最后需要的实现类的数目应该是:3 + 3 = 6个。
??????? 这也从侧面体现了,使用对象组合的方式比继承要来得更灵活。


(8)桥接模式的调用顺序示意图
??????? 桥接模式的调用顺序如图8所示:

研磨设计方式之桥接模式-3
????????????????????? 图8? 桥接模式的调用顺序示意图

3.2? 谁来桥接

??????? 所谓谁来桥接,就是谁来负责创建抽象部分和实现部分的关系,说得更直白点,就是谁来负责创建Implementor的对象,并把它设置到抽象部分的对象里面去,这点对于使用桥接模式来说,是十分重要的一点。
??????? 大致有如下几种实现方式:

由客户端负责创建Implementor的对象,并在创建抽象部分的对象的时候,把它设置到抽象部分的对象里面去,前面的示例采用的就是这个方式可以在抽象部分的对象构建的时候,由抽象部分的对象自己来创建相应的Implementor的对象,当然可以给它传递一些参数,它可以根据参数来选择并创建具体的Implementor的对象可以在Abstraction中选择并创建一个缺省的Implementor的对象,然后子类可以根据需要改变这个实现也可以使用抽象工厂或者简单工厂来选择并创建具体的Implementor的对象,抽象部分的类可以通过调用工厂的方法来获取Implementor的对象如果使用IoC/DI容器的话,还可以通过IoC/DI容器来创建具体的Implementor的对象,并注入回到Abstraction中

下面分别给出后面几种实现方式的示例。
1:由抽象部分的对象自己来创建相应的Implementor的对象
????????对于这种情况的实现,又分成两种,一种是需要外部传入参数,一种是不需要外部传入参数。

?????? (1)从外面传递参数比较简单,比如前面的示例,如果用一个type来标识具体采用哪种发送消息的方案,然后在Abstraction的构造方法中,根据type进行创建就好了。

??????? 还是代码示例一下,主要修改Abstraction的构造方法,示例代码如下:


?图9? 基于JDBC开发的应用程序结构示意图
?

??????? 那么这些JDBC的API,谁去实现呢?光有接口,没有实现也不行啊。
??????? 该驱动程序登场了,JDBC的驱动程序实现了JDBC的API,驱动程序就相当于桥接模式中的具体实现部分。而且不同的数据库,由于数据库实现不一样,可执行的Sql也不完全一样,因此对于JDBC驱动的实现也是不一样的,也就是不同的数据库会有不同的驱动实现。此时驱动程序这边的程序结构如图10所示:

研磨设计方式之桥接模式-3
????????? ?图10? 驱动程序实现结构示意图
??????????

????????有了抽象部分——JDBC的API,有了具体实现部分——驱动程序,那么它们如何连接起来呢?就是如何桥接呢?
??????? 就是前面提到的DriverManager来把它们桥接起来,从某个侧面来看,DriverManager在这里起到了类似于简单工厂的功能,基于JDBC的应用程序需要使用JDBC的API,如何得到呢?就通过DriverManager来获取相应的对象。
??????? 那么此时系统的整体结构如图11所示:

研磨设计方式之桥接模式-3
?????????????????????????????? 图11? JDBC的结构示意图
?

??????? 通过上图可以看出,基于JDBC的应用程序,使用JDBC的API,相当于是对数据库操作的抽象的扩展,算作桥接模式的抽象部分;而具体的接口实现是由驱动来完成的,驱动这边自然就相当于桥接模式的实现部分了。而桥接的方式,不再是让抽象部分持有实现部分,而是采用了类似于工厂的做法,通过DriverManager来把抽象部分和实现部分对接起来,从而实现抽象部分和实现部分解耦。?

??????? JDBC的这种架构,把抽象和具体分离开来,从而使得抽象和具体部分都可以独立扩展。对于应用程序而言,只要选用不同的驱动,就可以让程序操作不同的数据库,而无需更改应用程序,从而实现在不同的数据库上移植;对于驱动程序而言,为数据库实现不同的驱动程序,并不会影响应用程序。而且,JDBC的这种架构,还合理的划分了应用程序开发人员和驱动程序开发人员的边界。
??????? 对于有些朋友会认为,从局部来看,体现了策略模式,比如在上面的结构中去掉“JDBC的API和基于JDBC的应用程序”这边,那么剩下的部分,看起来就是一个策略模式的体现。此时的DriverManager就相当于上下文,而各个具体驱动的实现就相当于是具体的策略实现,这个理解也不算错,但是在这里看来,这么理解是比较片面的。
??????? 对于这个问题,再次强调一点:对于设计模式,要从整体结构上、从本质目标上、从思想体现上来把握,而不要从局部、从表现、从特例实现上来把握。

?

?

?

未完待续??

?

     楼主牛人。 LZ继续努力,我们很多人是都是你的忠实粉丝。加油!     
有图有真相  顶 顶 顶    }
}

class UrgencyMessage extends Message
{
    UrgencyMessage(String msg, String usr)
    {
        super("UrgencyMessage:" + msg, usr);
    }
}

interface Send
{
    public void send(Message ms);
}

class Mail implements Send
{
    public void send(Message ms)
    {
        System.out.println("mail " + ms.message + " to " + ms.user);
    }
}

class Sms implements Send
{
    public void send(Message ms)
    {
        System.out.println("sms " + ms.message + " to " + ms.user);
    }
}    }
}

class UrgencyMessage extends Message
{
    UrgencyMessage(String msg, String usr)
    {
        super("UrgencyMessage:" + msg, usr);
    }
}

interface Send
{
    public void send(Message ms);
}

class Mail implements Send
{
    public void send(Message ms)
    {
        System.out.println("mail " + ms.message + " to " + ms.user);
    }
}

class Sms implements Send
{
    public void send(Message ms)
    {
        System.out.println("sms " + ms.message + " to " + ms.user);
    }
}

不可以像你说的那样去设计。你要搞清楚桥接模式的适用范围:
1:具有抽象部分和具体实现部分
2:抽象部分会使用具体实现部分
3:抽象部分和具体实现部分需要独立的变化
4:抽象部分和具体实现部分可以任意组合

    要注意是抽象部分和具体实现部分,而不是数据和算法,抽象部分和具体实现部分都是指的行为。你可以再好好的体会一下桥接模式的含义和为何要像桥接那样设计。    }
}

class UrgencyMessage extends Message
{
    UrgencyMessage(String msg, String usr)
    {
        super("UrgencyMessage:" + msg, usr);
    }
}

interface Send
{
    public void send(Message ms);
}

class Mail implements Send
{
    public void send(Message ms)
    {
        System.out.println("mail " + ms.message + " to " + ms.user);
    }
}

class Sms implements Send
{
    public void send(Message ms)
    {
        System.out.println("sms " + ms.message + " to " + ms.user);
    }
}

不可以像你说的那样去设计。你要搞清楚桥接模式的适用范围:
1:具有抽象部分和具体实现部分
2:抽象部分会使用具体实现部分
3:抽象部分和具体实现部分需要独立的变化
4:抽象部分和具体实现部分可以任意组合

    要注意是抽象部分和具体实现部分,而不是数据和算法,抽象部分和具体实现部分都是指的行为。你可以再好好的体会一下桥接模式的含义和为何要像桥接那样设计。

如果仅仅就楼主这个发送信息的例子而言,桥接模式确实可以很好的解决问题,但是我写的这个实现都可以解决桥接模式1中所提到的问题,也都符合OCP原则。楼主能不能觉体解释一下为什么不能这样来设计的具体原因,这样会有什么问题?





    }
}

class UrgencyMessage extends Message
{
    UrgencyMessage(String msg, String usr)
    {
        super("UrgencyMessage:" + msg, usr);
    }
}

interface Send
{
    public void send(Message ms);
}

class Mail implements Send
{
    public void send(Message ms)
    {
        System.out.println("mail " + ms.message + " to " + ms.user);
    }
}

class Sms implements Send
{
    public void send(Message ms)
    {
        System.out.println("sms " + ms.message + " to " + ms.user);
    }
}

不可以像你说的那样去设计。你要搞清楚桥接模式的适用范围:
1:具有抽象部分和具体实现部分
2:抽象部分会使用具体实现部分
3:抽象部分和具体实现部分需要独立的变化
4:抽象部分和具体实现部分可以任意组合

    要注意是抽象部分和具体实现部分,而不是数据和算法,抽象部分和具体实现部分都是指的行为。你可以再好好的体会一下桥接模式的含义和为何要像桥接那样设计。

如果仅仅就楼主这个发送信息的例子而言,桥接模式确实可以很好的解决问题,但是我写的这个实现都可以解决桥接模式1中所提到的问题,也都符合OCP原则。楼主能不能觉体解释一下为什么不能这样来设计的具体原因,这样会有什么问题?







   你的设计其实非常类似于桥接,比如你的Message类就相当于桥接模式的抽象部分的顶层抽象类,而send接口就相当于桥接模式的具体实现部分,看起来好像可以,是这样的吗?
    看看你是如何桥接的呢?其实是通过在方法里面传入桥接的抽象部分,也就是说,你的桥接方式是把抽象部分传入到具体实现里面,跟桥接模式是反过来的。
    这么做有很多问题,简单列举几个:
1:你实现的抽象部分和具体实现部分是耦合的,由于你抽象部分没有接口定义,请问在实现部分如何回调?尤其是当你的抽象部分有扩展的时候
2:你从抽象部分调用具体实现部分的时候,你如何保证多次调用,使用的是同一个抽象部分的实例。桥接模式是一旦桥接成功,在抽象部分使用具体实现的时候,是使用一套的,比如具体实现是DB实现,那么抽象部分使用的时候所有的方法都来自于DB实现
3:在你的如何实现抽象部分和具体部分的多对多控制?

    好了,先简单列举这么几个吧,好好体会一下桥接模式本身,再看看你的设计,问题自然就出来了。
/** * 以Email的方式发送消息 */public class MessageEmail implements MessageImplementor{public void send(String message, String toUser) {System.out.println("使用Email的方式,发送消息'" +message+"'给"+toUser);}}

貌似桥接模式要求任何一个 MessageImplementor的实现都具备一套代码处理所有AbstractMessage的能力,如上面的send方法,不管AbstractMessage的具体子类是什么,普通的也好,加急的也好,它都不需要改变代码统统能处理,只要传参数message时传适当的值就行了。但假如实际需求是,对于不同的AbstractMessage子类, 要求MessageImplementor的实现类比如 MessageEmail的实现是截然不同,没有多少规律可循的,比如,对于加急消息,要求在第一行打印几个感叹号,而对于普通信息实现维持不变。这时就不能通过改变参数message的值来实现不同的功能了,这样的话还能用桥接模式吗? 20 楼 xueyi_lee 2011-08-04   楼主真是牛人啊。。膜拜

热点排行