标签:
设计模式 -- 桥接模式(Bridge)
------------------------------------------------------------------
写在前面的话
话说,泡菜君最近的工作特别的卖力,每天早晨多早的来到办公室,晚上都在樊姐走后才走,感觉整个人就跟打了鸡血一样 .... 由此不得不感叹美女颜值的魅力...
今天早上泡菜君还是一大早就来到办公室, 老习惯 --> 开电脑、倒热水、泡茶. 先看看新浪体育、汽车之家, 这时樊经理(大美女)也来到办公室,樊姐今天一身运动装,白色T恤、蓝色紧身牛仔裤、耐克运动鞋, 泡菜君的目光随着樊姐的进来而被吸引住了,这时因为离上班时间还有一会 办公室的其他都同事都还没有来。樊姐把电脑打开后,就把她手里的牛奶放到了一杯热水里面去加热 .... , 过了大概5分钟,这时泡菜君起身来到饮水机旁倒茶水,樊姐温柔的对着泡菜君说:‘帮我摸摸我的奶热了吗?‘, ... 泡菜君一听这话 瞬间脸红心跳、六神无主 ,心里想 ‘不是吧,摸摸你的奶 ... 这不太好吧... 毕竟是在办公室...‘, 樊姐见泡菜君半天没反应 便又说道 ‘快点啦,帮我摸摸我的奶 看热了吗?‘, 泡菜君因没注意到樊姐热牛奶的事情 还没反应过来这是怎么回事, 便对樊姐说 ‘这不太好吧.... 我不敢摸‘, 樊姐指着桌上的牛奶又说 ‘有啥不敢摸的,又没让你喝...‘ , 泡菜君这才明白了原来是让他摸摸樊姐热的牛奶热了没有.....
-----------------------------------------------------------------
经过上午的这么一出意外,泡菜君上班时一直不在状态... 大概上午10点过,樊姐召集项目组的成员开个短会讨论一下公司OA系统有一个功能需要改的.
项目现状: 公司OA系统目前有一个模块是发送消息业务,能发送普通消息,发送的方式有: 系统内短消息、邮件消息
希望改进:增加发送 ‘加急消息‘, ‘加急消息‘会自动在消息上添加‘加急‘的文字描述,然后再发送消息,并且加急消息会提供监控的方法,让客户端可以随时通过这个方法来了解对于加急消息的处理的进度.
目前就这么一个需求改动,让大家讨论一下怎么来实现....,最后经过激烈的讨论, 这个任务落到了泡菜君的头上,因为项目组的其他同事手头上还有其他的项目需要赶..., 会议结束后,樊姐让大伙回去抓紧干活,但是呢.... 把泡菜君留下来了..., 项目组同事走后 办公室里面只剩下樊姐和泡菜君两个人独处一室.... (这画面 ... 感觉有点想歪了.), 泡菜君目不转睛的盯着樊姐脖子下面的凸出部分(目测是E zhao), 樊姐被泡菜君这样一直盯着便有点脸红起来, 故作大声的说道 ‘ 看什么呢? 入神了哦?‘ , 泡菜君这才反应过来... 是有点入神了.
樊姐对泡菜君交待到: ‘下去好好想想这个功能怎么改进,后面公司的同事可都会用这个OA系统呢,好好设计下,别给我们项目组丢脸...‘
泡菜君回到位子上面,还是老规矩 通过SVN把公司OA系统的代码下载下来,看看目前的功能实现: 目前OA系统只实现了发送普通消息 : 系统内短消息和邮件.
系统的结构示意图如下:
OA系统目前代码(只是简单示例一下)
(1) 消息的统一接口
/** * 消息的统一接口 * @author Administrator */ public interface Message { /** * 发送消息 * @param message 要发送的消息内容 * @param toUser 消息发送的目的人员 */ public void send(String message,String toUser); }
(2)系统内短消息
/** * 以站内短消息的方式发送普通消息 * @author Administrator */ public class CommonMessageSMS implements Message { @Override public void send(String message, String toUser) { System.out.println("使用站内短消息的方式,发送消息 ‘" + message +"‘ 给" + toUser); } }
(3)系统内邮件消息
/** * 以E-mail的方式发送普通消息 * @author Administrator */ public class CommonMessageEmail implements Message { @Override public void send(String message, String toUser) { System.out.println("使用E-mail的方式,发送消息 ‘" + message +"‘ 给" + toUser); } }
看了目前公司OA系统的代码实现,泡菜君想... 增加发送加急消息的功能,其实也没有那么难嘛...通过简单的扩展可以实现了,于是他飞快的画了一个扩展功能的系统结构图,如下:
(1)扩展出来的加急消息的接口
/** * 加急消息的抽象接口 * @author Administrator */ public interface UrgencyMessage extends Message { /** * 监控某消息的处理过程 * @param messageId 被监控的消息的编号 * @return 包含监控到的数据对象 */ public Object watch(String messageId); }
(2)加急消息 站内通知实现
/** * 加急消息 站内通知方式 * @author Administrator * */ public class UrgencyMessageSMS implements UrgencyMessage { @Override public void send(String message, String toUser) { message = "加急:" + message; System.out.println("使用站内短消息的方式,发送消息 ‘" + message +"‘ 给" + toUser); } @Override public Object watch(String messageId) { //获取相应的数据,组织成监控的数据对象,然后返回 return null; } }
(3)加急消息 邮件通知实现
/** * 加急消息 邮件通知实现 * @author Administrator */ public class UrgencyMessageEmail implements UrgencyMessage { @Override public void send(String message, String toUser) { message = "加急:" + message; System.out.println("使用E-mail的方式,发送消息 ‘" + message +"‘ 给" + toUser); } @Override public Object watch(String messageId) { //获取相应的数据,组织成监控的数据对象,然后返回 return null; } }
泡菜君把上面的功能实现了以后,便来到樊姐的办公室, 他站在樊姐的旁边给她讲解他的实现思路和实现的代码 .... 这时樊姐让泡菜君坐下来,因为目前的需求可能还有一点点的变动, 按照以前项目经理说这话的时候 泡菜君心里面是拒绝的, 但是这次不知是怎么的,泡菜君心里不但没有反抗情绪 而且还很高兴的问道 ‘需求有什么变动呢‘, 樊姐依然用她那温柔的声音、深情的眼神看着泡菜君 给他说需求的变动地方 (泡菜君此时面对眼前这个楚楚动人的樊姐已经没有了任何的防御能力,任凭她提任何的需求改动.).
公司OA系统消息通知功能需求变更:希望添加特急消息通知,对于特急消息是在普通消息的处理基础上添加催促功能,消息通知的方式增加 ‘短信通知‘.
樊姐让泡菜君回去先设计一下该怎么做,然后把设计方案给她看看... 泡菜君回到自己的电脑前来,心里想着还是按照前面 实现 ‘加急消息‘ 的方式来实现 ‘特急消息‘,他画出来系统的结构图:
但是每种消息通知方式还需要增加 ‘短信通知‘的功能,为此泡菜君又再上面的基础上设计了新的系统结构图:
泡菜君把他实现的思路和画出来系统结构图拿到樊姐的办公室给她看看, 樊姐看了泡菜君的设计过后 思考了一会, 对着泡菜君嘟了嘟嘴 说道 ‘你觉得你的这种实现方式怎么样?‘ 泡菜君也说不出他的设计哪里好,哪里不好, 便回答到 ‘功能基本上可以是实现了‘, 樊姐说 ‘你可别有这样的想法,功能基本实现了是没有错,但是你能不能设计得更好一些呢,能不能活好点, 技巧多一点呢?‘ , 泡菜君一听这话便立刻来劲了 心里暗想 ‘让我活好点, 技巧多点.... 樊姐你不亲自试试怎么知道呢?‘ 此时泡菜君陷入了短暂的意淫阶段..., 樊姐打断了泡菜君的猥琐思想说道 ‘关于桥接姿势(模式)(Brid ge)你了解多少?‘ , 泡菜君因还没走刚才的幻觉中走出来 心想 ‘樊姐说的这是什么姿势 .. 桥接 ... 可我只知道 老汉推车、观音坐莲啊.. ‘(脸上此时还挂着略显猥琐的笑容), 樊姐见状有点生气大声说道‘你到底了解多少?‘ 这才把泡菜君从刚才的意淫阶段拉了回来, 他便急忙回到到 ‘樊姐, 这姿势(模式)我不是特别了解... 希望樊姐教教我,最好亲自以身示范下.....‘。 樊姐也是一脸的无赖 谁让樊姐人好、声甜、耐心足 (就是不知道活好不好...) 面对着眼前的这个实习生她也是尽力的多给予帮助... 樊姐拿笔起身 转向 会议板 首先给泡菜君的设计指出不足的地方,然后讲解关于‘桥接模式‘的基础知识.
泡菜君上面实现方式的不足: 采用通过继承来扩展的实现方式,有个明显的缺点,扩展消息的种类不太容易。不同种类的消息具有不同的业务,也就是有不同的实现,在这种情况下,每个种类的消息,需要实现所有不同的消息发送方式, 更可怕的是, 如果要新加入一种消息的发送方式,那么会要求所有的消息种类都要加入这种新的发送方式的实现.(要是考虑业务功能上的再扩展? 比如,要求实现群发消息,也就是一次可以发送多条消息,这就意味着很多地方得修改...)
桥接模式的基础知识
桥接模式的定义 : 将抽象部分与它的实现部分分离,使它们都可以独立地变化.
应用桥接模式来解决问题的思路 : 仔细分析上面泡菜君的实现方式的变化具有两个维度,一个维度是抽象的消息这边,包括普通消息、加急消息、特急消息,这几个抽象的消息本身就具有一定的关系,加急消息和特急消息会扩展平台消息; 另一个维度是在具体的消息发送方式上,包括 站内短消息、E-mail消息、手机短信消息,这几个方式是平等的。 这两个维度一共可以组合出9种不同的可能性来,它们的关系如下图:
泡菜君的实现方式导致程序扩展困难的根本原因在于: 消息的抽象和实现是混杂在一起的,这就导致了一个维度的变化会引起另一个维度进行相应的变化,从而使得程序扩展起来非常困难. 要想解决这个问题,就必须把这两个维度分开,也就是将抽象部门和实现部分分开,让它们相互独立,这样就可以实现独立的变化,使扩展变得简单.
桥接模式通过引入实现的接口,把实现部分从系统中分离出去,桥接模式的结构和说明如下:
(1)Abstraction : 抽象部分的接口。通常在这个对象中,要维护一个实现部分的对象引用,抽象对象里面的方法,需要调用实现部分的对象来完成。这个对象中的方法,通常都是和具体的业务相关的方法.
(2)RefinedAbstraction : 扩展抽象部分的接口。通常在这些对象中,定义跟实际业务相关的方法,这些方法的实现通常会使用Abstraction中定义的方法,也可能需要调用实现部分的对象来完成.
(3)Implementor : 定义实现部分的接口,这个接口不用和Abstraction中的方法一致,通常是由Implementor接口提供基本的操作。
(4)ConcreteImplementor : 真正实现Implementor接口的对象.
桥接模式示例代码
(1)Implementor接口的定义
/** * 定义实现部分的接口 * @author Administrator * */ public interface Implementor { /** * 示例方法,实现抽象部分需要的某些具体功能 */ public void operationImpl(); }
(2)Abstraction接口的定义
/** * 抽象部分的接口 * @author Administrator * */ public abstract class Abstraction { //持有一个实现部分的对象 protected Implementor impl; /** * 构造方法,传入实现部分的对象 * @param impl 实现部分的对象 */ public Abstraction(Implementor impl){ this.impl = impl; } /** * 示例操作,实现一定的功能 */ public void operation(){ impl.operationImpl(); } }
(3)具体的实现A
/** * 真正的具体实现对象 * @author Administrator * */ public class ConcreteImplementorA implements Implementor { @Override public void operationImpl() { //真正的实现 } }
(4)具体的实现B
/** * 真正的具体实现对象 * @author Administrator */ public class ConcreteImplementorB implements Implementor { @Override public void operationImpl() { //真正的实现 } }
(5)扩展Abstraction接口的对象实现
/** * 扩充由Abstraction定义的接口功能 * @author Administrator */ public class RefinedAbstraction extends Abstraction { public RefinedAbstraction(Implementor impl) { super(impl); } /** * 示例操作,实现一定的功能 */ public void otherOperation(){ //实现一定的功能,可能会使用具体实现部分的实现方法 //但是本方法更大的可能是使用Abstraction中定义的方法 //通过组合使用Abstraction中定义的方法来完成更多的功能 } }
樊姐把桥接模式的基础知识都给泡菜君讲完了, 泡菜君看着樊姐的样子... 心想:‘此女不光颜值高,技术活一点也不差呀,真是让人佩服的不行不行的, 心里不由对樊姐增加了几分钦佩之情..‘, 泡菜君回到自己的座位上思考着怎么用桥接模式把这个需求重新设计一下.
泡菜君分析要使用桥接模式来重新实现前面的示例,首要任务就是要把抽象部分和实现部分分离出来,分析要实现的功能。抽象部分就是各个消息的类型所对应的功能,而实现部分就是各种发送消息的方式。其次要按照桥接模式的结构,给抽象部分和实现部分分别定义接口,然后分别实现它们就可以了.
1.从简单的功能开始
从相对简单的功能开始,先实现普通消息和加急消息的功能,发送方式先实现站内短信消息和E-mail两种.
使用桥接模式来实现这些功能的程序结构如图:
下面是具体的代码实现:
(1)实现部分定义的接口
/** * 实现发送消息的统一接口 * @author Administrator */ public interface MessageImplementor { /** * 发送消息 * @param message 要发送的消息内容 * @param toUser 消息发送的目的人员 */ public void send(String message,String toUser); }
(2)抽象部分定义的接口
/** * 抽象的消息对象 * @author Administrator */ public abstract class AbstractMessage { //持有一个实现部分的对象 protected MessageImplementor impl; /** * 构造方法,传入实现部分的对象 * @param impl 实现部分的对象 */ public AbstractMessage(MessageImplementor impl){ this.impl = impl; } /** * 发送消息,转调实现部分的方法 * @param message 要发送的消息 * @param toUser 接收消息人员 */ public void sendMessage(String message,String toUser){ this.impl.send(message, toUser); } }
(3)站内短消息的实现
/** * 以站内短消息的方式发送消息 * @author Administrator */ public class MessageSMS implements MessageImplementor { @Override public void send(String message, String toUser) { System.out.println("使用站内短消息的方式,发送消息‘"+message+"‘给" + toUser); } }
(4)E-mail方式的实现
/** * 以E-mail的方式发送消息 * @author Administrator */ public class MessageEmail implements MessageImplementor { @Override public void send(String message, String toUser) { System.out.println("使用E-mail的方式,发送消息‘"+message+"‘给" + toUser); } }
(5)扩展抽象的消息接口,普通消息的实现
public class CommonMessage extends AbstractMessage { public CommonMessage(MessageImplementor impl) { super(impl); } @Override public void sendMessage(String message, String toUser) { //对于普通消息,什么都不干,直接调用父类的方法,把消息发送出去就可以了 super.sendMessage(message, toUser); } }
(6)扩展抽象的消息接口,加急消息的实现
public class UrgencyMessage extends AbstractMessage { public UrgencyMessage(MessageImplementor impl) { super(impl); } @Override public void sendMessage(String message, String toUser) { message = "加急:" + message; super.sendMessage(message, toUser); } /** * 扩展自己的新功能:监控某消息的处理过程 * @param messageId 被监控的消息的编号 * @return 包含监控到数据对象 */ public Object watch(String messageId){ //获取相应的数据,组织成监控的数据对象,然后返回 return null; } }
2.添加功能
上面运用了桥接模式来实现了普通消息和加急消息的站内发生、邮件发送,现在需要添加新的功能: 特急消息、使用手机发送消息的方式,该怎么实现呢?
很简单,只需要在抽象部分再添加一个特急消息的类,扩展抽象消息就可以把特急消息的处理功能加入到系统中;对于添加手机发送消息的方式也很简单,在实现部分新增加一个实现类,实现用手机发送消息的方式就可以了.(采用桥接模式来实现,抽象部分和实现部分分离开了,可以相互独立地变化,而不会相互影响。因此在抽象部分添加新的消息处理,对发送消息的实现部分是没有影响的;反过来增加发送消息的方式,对消息处理部分也是没有影响的.)
下面是具体的代码:
(1)新的特急消息的处理类
public class SpecialUrgencyMessage extends AbstractMessage { public SpecialUrgencyMessage(MessageImplementor impl) { super(impl); } public void hurry(String messageId){ //执行催促的业务,发出催促的消息 } @Override public void sendMessage(String message, String toUser) { message = "特急:" + message; super.sendMessage(message, toUser); } }
(2)使用手机短信消息的方式发送消息
/** * 以手机短消息的方式发送消息 * @author Administrator */ public class MessageMobile implements MessageImplementor { @Override public void send(String message, String toUser) { System.out.println("使用手机短消息的方式,发送消息‘"+message+"‘给" + toUser); } }
(3)测试功能
public class Client { public static void main(String[] args) { //创建具体的实现对象 MessageImplementor impl = new MessageSMS(); //创建一个普通的消息对象 AbstractMessage m = new CommonMessage(impl); m.sendMessage("请喝一杯茶", "小李"); System.out.println("------------------------"); //创建一个紧急消息对象 m = new UrgencyMessage(impl); m.sendMessage("请喝一杯茶", "小李"); System.out.println("------------------------"); //创建一个特急消息对象 m = new SpecialUrgencyMessage(impl); m.sendMessage("请喝一杯茶", "小李"); System.out.println("------------------------"); //把实现方式切换成手机短消息,然后再实现一遍 impl = new MessageMobile(); m = new CommonMessage(impl); m.sendMessage("请喝一杯茶", "小李"); System.out.println("------------------------"); m = new UrgencyMessage(impl); m.sendMessage("请喝一杯茶", "小李"); System.out.println("------------------------"); m = new SpecialUrgencyMessage(impl); m.sendMessage("请喝一杯茶", "小李"); } }
运行结果:
使用站内短消息的方式,发送消息‘请喝一杯茶‘给小李 ------------------------ 使用站内短消息的方式,发送消息‘加急:请喝一杯茶‘给小李 ------------------------ 使用站内短消息的方式,发送消息‘特急:请喝一杯茶‘给小李 ------------------------ 使用手机短消息的方式,发送消息‘请喝一杯茶‘给小李 ------------------------ 使用手机短消息的方式,发送消息‘加急:请喝一杯茶‘给小李 ------------------------ 使用手机短消息的方式,发送消息‘特急:请喝一杯茶‘给小李
泡菜君用桥接模式把需求重新写了过后觉得对于该模式自己还不是特别了解,所以特意在网上查询了一下关于桥接模式的基础知识:
认识桥接模式
1.什么是桥接?
所谓桥接,通俗点说就是在不同的东西之间搭一个桥,让它们能够连接起来,可以相互通讯和使用,在桥接模式中是给 被分离了的抽象部分和实现部分来搭桥.(PS:在桥接模式中的桥接是单向的,也就是只能是抽象部分的对象去使用具体实现部分的对象,而不能反过来,也就是单向桥.)
2.为何需要桥接?
使用桥接的目的是为了达到让抽象部分和实现部分都可以达到独立变化的目的,在桥接模式中,是把抽象部分和实现部分分离开来的,虽然从程序结构上是分开了,但是在抽象部分实现的时候,还是需要使用具体的实现的, 抽象部分如何才能调用到具体实现部分的功能呢? ---> 很简单,搭个桥就可以了。搭个桥,让抽象部分通过这个桥就可以调用到实现部分的功能了,因此需要桥接.
3.如何桥接?
只要让抽象部分拥有实现部分的接口对象,就桥接上了,在抽象部分即可通过这个接口来调用具体实现部分的功能。也就是说,桥接在程序上体现了在抽象部分拥有实现部分的接口对象,维护桥接就是维护这个关系.
4.独立变化
桥接模式的意图是使得抽象和实现可以独立变化,都可以分别扩充。也就是说抽象部分和实现部分是一种非常松散的关系。从某个角度来讲,抽象部分和实现部分是可以完全分开的、独立的,抽象部分不过是一个使用实现部分对外接口的程序罢了.
桥接模式的调用顺序示意图:
谁来桥接
所谓谁来桥接,就是谁来负责创建抽象部分和实现部分的关系,说得更直白点,就是谁来负责创建Implementor对象,并把它设置到抽象部分的对象中去.
大致有如下几种实现方式:
(1)由客户端负责创建Implementor对象,并在创建抽象部分对象的时候,把它设置到抽象部分的对象中去.
(2)可以在抽象部分对象构建的时候,由抽象部分的对象自己来创建相应的Implementor对象,当然可以给它传递一些参数,它可以根据参数来选择并创建具体的Implementor对象.
(3)可以在Abstraction中选择并创建一个默认的Implementor对象,然后子类可以根据需要改变这个实现
(4)可以使用抽象工厂或者简单工厂来选择并创建具体的Implementor对象,抽象部分的类可以通过调用工厂的方法来获取Implementor对象
(5)使用IoC/DI容器来创建具体的Implementor对象,并注入回到Abstraction中.
典型例子 --- JDBC
在Java应用中,对于桥接模式有一个非常典型的例子,就是应用程序使用JDBC驱动程序进行开发的方式.
使用JDBC进行开发程序,简单的片段代码示例如下:
String sql = "具体要操作的sql语句"; //1:装载驱动 Class.forName("驱动的名字"); //2:创建连接 Connection conn = DriverManager.getConnection("连接数据库服务的URL","用户名","密码"); //3:创建statement或者是preparedStatement PreparedStatement pstmt = conn.prepareStatement(sql); //4:执行sql,如果是查询,再获取ResultSet ResultSet rs = pstmt.executeQuery(sql); //5:循环从ResultSet中把值取出来,封装到数据对象中去 while(rs.next()){ //取值示意,按名字取值 String uuid = rs.getString("uuid"); } //6:关闭 rs.close(); pstmt.close(); conn.close();
从上面程序可以看出是面向JDBC的API开发,这些接口就相当于桥接模式中抽象部分的接口,此时的系统结构如下图:
那么这些JDBC的API,谁去实现呢?光有接口,没有实现也不行啊. ---> 此时该驱动程序登场了,JDBC的驱动程序实现了JDBC的API,驱动程序就相当于桥接模式中具体实现部分。不同的数据库有不用的驱动程序。 此时驱动程序的程序结构如下图:
有了抽象部分---JDBC的API和具体实现部分---驱动程序,那么它们如何连接起来呢?就是如何桥接呢?
是通过DriverManager来把它们桥接起来,(从某个侧面来看,DriverManager在这里起到了类似于简单工厂的功能,基于JDBC的应用程序需要使用JDBC的API,如何得到呢? --->就是通过DriverManager来获取相应的对象).
此时系统的整体结构如下:
从上图可以看出,基于JDBC的应用程序,使用JDBC的API,相当于是对数据库操作的抽象扩展,算做桥接模式的抽象部分; 而具体的接口实现是由驱动来完成的,驱动就相当于桥接模式的实现部分了。 而桥接的方式,不再是让抽象部分持有实现部分,而是采用了类似于工厂的做法,通过DriverManager来把抽象部分和实现部分对接起来,从而实现抽象部分和实现部分解耦。 JDBC的这种架构,把抽象部分和具体部分分离开来,从而使得抽象部分和具体部分都可以独立扩展。
桥接模式的本质 : 分离抽象和实现. 桥接模式最重要的工作就是分离抽象部分和实现部分,这是解决问题的关键。只有把抽象部分和实现部分分离开了,才能够让它们独立地变化;只有抽象部分和实现部分可以独立地变化,系统才会有更好的可扩展性和可维护性.
----------------------------------------------------------------------------------------------------------------------------------
参考:《研磨设计模式》一书
后话: 因个人水平有限,如有不足之处,希望大家给我指出,以求共同学习,共同进步。
标签:
原文地址:http://www.cnblogs.com/xinhuaxuan/p/5565619.html