目录
一、什么是桥接模式
桥接(Bridge)模式是构造型的设计模式之一。桥接模式基于类的最小设计原则,通过使用封装,聚合以及继承等行为来让不同的类承担不同的责任。它的主要特点是把抽象(abstraction)与行为实现(implementation)分离开来,从而可以保持各部分的独立性以及应对它们的功能扩展。
二、桥接模式的应用场景
《研磨设计模式》中给出这么一个场景:发送消息。
从消息的重要程度上来看,消息可以分为普通消息、加急消息两种,两种消息的业务功能处理是不一样的,比如普通消息直接发送即可,而加急消息除了需在发送的消息上添加加急标识外,还需提供监控功能,以方便客户端了解加急消息的处理进度。
从发送消息的方式上来看,消息又可以分为站内短消息和E-Mail两种。
在不使用设计模式的情况下,我们一般会这么做:
考虑到普通消息和加急消息都各有站内短消息和E-Mail两种发送方式,为了让外部能统一操作,因此把消息设计成接口,普通消息的两种发送方式分别作为该接口的两个实现,但加急消息除了有基本的发送消息的功能,还需要添加监控功能,因此将加急消息也抽取为接口,加急消息的两种发送方式做为接口的两个实现。
这个时候,系统的类图结构设计如下。
仔细观察这个类图,会发现一个很明显的问题,通过这种继承的方式来扩展消息处理,会非常不方便。举个例子,假如我们要在此基础上添加一个特急消息的处理:特急消息不需要进行监控,只要没有完成,就直接催促,也就是说,对于特急消息,在普通消息的基础上,需要添加催促的功能。而特急消息的发送方式还是发送站内短消息和E-Mail两种,此时的类图结构如下。
从以上过程可以看到,我们在实现加急消息处理的时候,必须实现站内短消息和E-Mail两种处理方式;在实现特急消息处理的时候,又必须实现站内短消息和E-Mail两种处理方式。
这意味着,以后每次扩展一种消息处理,都必须要实现站内短消息和E-Mail两种处理方式,是不是很痛苦?
这还不算完,如果要添加新的消息发送方式呢?继续扩展功能,添加手机短消息的发送方式,类图结构扩展如下。
仔细观察现在的实现,如果此时再添加一种消息处理,则需要添加系统内短消息、邮件和手机消息三个实现。按照这种实现方式继续扩展下去,消息类的数量会急剧增加,极易出现“类膨胀”。
遇到类似以上这种情况,应该如何解决呢?答案:使用桥接模式。
三、桥接模式的结构
仔细分析上面的示例,会发现示例中消息的变化具有两个维度,一个维度是抽象的消息这边,包括普通消息、加急消息和特急消息;另一个维度是在消息的发送方式上,包括站内短消息、E-Mail和手机短消息。这两个维度一共可以组合出9种不同的可能性来,它们的关系如下。
现在出现问题的根本原因,就在于消息的抽象和实现是混杂在一起的,这就导致一个维度的变化会引起另一个维度进行相应的变化,从而使得程序扩展起来非常困难。 要解决这个问题,就必须把这两个维度分开,也就是将抽象部分和实现部分分开,让它们相互独立,这样就可以实现独立的变化,使扩展变得简单。桥接模式就可以达到这种效果,以下是桥接模式的结构示意图。
桥接模式涉及的角色及其职责如下:
Abstraction:抽象部分的接口。通常在这个对象中,要维护一个实现部分的对象引用,抽象对象里面的方法,需要调用实现部分的对象来完成,这个对象中的方法,通常都是和具体的业务相关的方法。
RefinedAbstraction:扩展抽象部分的接口。通常在这些对象中,定义跟实际业务相关的方法,这些方法实现通常会使用Abstraction中定义的方法,也可能会调用实现部分的对象来完成。
Implementor:定义实现部分的接口。这个接口不用和Abstraction中定义的方法一致,通常是由Implementor接口提供基本的操作。而 Abstraction中定义的是基于这些基本操作的业务方法,也就是说 Abstraction定义了基于这些基本操作的较高层次的操作。
ConcreteImplementor:真正实现 Implementor接口的对象。 桥接模式结构示意源代码如下:
public abstract class Abstraction {
/**
* 持有一个实现部分的对象
*/
protected Implementor impl;
/**
* 构造方法,传入一个实现部分的对象
*/
public Abstraction(Implementor impl) {
this.impl = impl;
}
/**
* 示例操作,实现一定的功能,可能需要调用实现部分的具体实现方法
*/
public void operation() {
impl.operationImpl();
}
}
public interface Implementor {
/**
* 示例方法,实现抽象部分需要的某些具体功能
*/
public void operationImpl();
}
public class RefinedAbstraction extends Abstraction {
public RefinedAbstraction(Implementor impl) {
super(impl);
}
/**
* 示例操作,实现一定的功能
*/
public void otherOperation() {
// 实现一定的功能,可能会使用具体实现部分的实现方法
// 但是本方法更大的可能是使用Abstraction中定义的方法
// 通过组合使用Abstraction中定义的方法来完成更多的功能
}
}
public class ConcreteImplementorA implements Implementor {
@Override
public void operationImpl() {
// 真正的实现
}
}
public class ConcreteImplementorB implements Implementor {
@Override
public void operationImpl() {
// 真正的实现
}
}
四、使用桥接模式重写消息示例
先实现普通消息和加急消息的功能,发送方式先实现站内短消息和E-mail两种,使用桥接模式来实现这些功能的类图结构如下。
下面是各个类的实现代码。
/**
* 抽象的消息对象
*/
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);
}
}
/**
* 实现发送消息的统一接口
*/
public interface MessageImplementor {
/**
* 发送消息
*
* @param message 消息的内容
* @param toUser 消息的接收人
*/
public void send(String message, String toUser);
}
public class CommonMessage extends AbstractMessage {
public CommonMessage(MessageImplementor impl) {
super(impl);
}
public void sendMessage(String message, String toUser){
//对于普通消息,什么都不干,直接调用父类的方法,把消息发送出去
super.sendMessage(message, toUser);
}
}
public class UrgencyMessage extends AbstractMessage {
public UrgencyMessage(MessageImplementor impl) {
super(impl);
}
public void sendMessage(String message, String toUser) {
// 对于加急消息,在消息上添加“加急”标识,再调用父类的方法,把消息发送出去
message = "[加急]" + message;
super.sendMessage(message, toUser);
}
/**
* 扩展自己的新功能:监控某消息的处理过程
* @param message 被监控的消息
* @return 包含监控到的数据对象,此处用Object示意
*/
public Object watch(AbstractMessage message) {
return null;
}
}
public class MessageSMS implements MessageImplementor {
@Override
public void send(String message, String toUser) {
System.out.println("使用站内短消息的方式,发送消息:"+message+"给"+toUser);
}
}
public class MessageEmail implements MessageImplementor {
@Override
public void send(String message, String toUser) {
System.out.println("使用E-mail的方式,发送消息:" + message + "给" + toUser);
}
}
继续添加对特急消息的处理,同时添加发送手机消息的方式,该如何实现?
很简单,只需要在抽象部分再添加一个特急消息的类,扩展抽象消息就可以把特急消息的处理功能加入到系统中;对于添加手机发送消息的方式也很简单,在实现部分新增加一个实现类,实现使用手机发送消息的方式就可以了。
新添加的两个类,代码如下。
public class SpecialUrgencyMessage extends AbstractMessage {
public SpecialUrgencyMessage(MessageImplementor impl) {
super(impl);
}
public void hurry(AbstractMessage message) {
// 执行催促的功能,发出催促的信息
}
public void sendMessage(String message, String toUser) {
// 对于特急消息,在消息上添加“特急”标识,再调用父类的方法,把消息发送出去
message = "[特急]" + message;
super.sendMessage(message, toUser);
// 还需要增加一条待催促的信息
}
}
public class MessageMobile implements MessageImplementor {
@Override
public void send(String message, String toUser) {
System.out.println("使用手机短消息的方式,发送消息:" + message + "给" + toUser);
}
}
写个客户端类来测试一下,代码如下。
public class Client {
public static void main(String[] args) {
// 创建具体的实现对象
MessageImplementor impl = new MessageSMS();
// 创建一个普通消息对象
AbstractMessage m = new CommonMessage(impl);
m.sendMessage("今晚8点,时代广场,不见不散!", "小乔");
// 创建一个紧急消息对象
m = new UrgencyMessage(impl);
m.sendMessage("半小时后召开紧急会议!", "小周");
// 创建一个特急消息对象
m = new SpecialUrgencyMessage(impl);
m.sendMessage("领导过来视察了,速回!", "小明");
// 把发送方式切换成手机短消息,将以上内容重发一遍
impl = new MessageMobile();
// 创建一个普通消息对象
m = new CommonMessage(impl);
m.sendMessage("今晚8点,时代广场,不见不散!", "小乔");
// 创建一个紧急消息对象
m = new UrgencyMessage(impl);
m.sendMessage("半小时后召开紧急会议!", "小周");
// 创建一个特急消息对象
m = new SpecialUrgencyMessage(impl);
m.sendMessage("领导过来视察了,速回!", "小明");
}
}
运行程序打印结果如下:
使用站内短消息的方式,发送消息:今晚8点,时代广场,不见不散!给小乔
使用站内短消息的方式,发送消息:[加急]半小时后召开紧急会议!给小周
使用站内短消息的方式,发送消息:[特急]领导过来视察了,速回!给小明
使用手机短消息的方式,发送消息:今晚8点,时代广场,不见不散!给小乔
使用手机短消息的方式,发送消息:[加急]半小时后召开紧急会议!给小周
使用手机短消息的方式,发送消息:[特急]领导过来视察了,速回!给小明
五、桥接模式的适用性
在以下的情况下可以考虑使用桥接模式:
1.如果你不希望在抽象部分和实现部分采用固定的绑定关系,可以采用桥接模式,来把抽象部分和实现部分分开,然后在程序运行期间来动态地设置抽象部分需要用到的具体的实现,还可以动态地切换具体的实现。
2.如果出现抽象部分和实现部分都能够扩展的情况,可以采用桥接模式,让抽象部分和实现部分独立地变化,从而灵活地进行单独扩展,而不是搅在一起,扩展一边就会影响到另一边。
3.如果希望实现部分的修改不会对客户产生影响,可以采用桥接模式。由于客户是面向抽象的接口在运行,实现部分的修改可以独立于抽象部分,并不会对客户产生影响,也可以说对客户是透明的。
4.如果采用继承的实现方案,会导致产生很多子类,对于这种情况,可以考虑采用桥接模式,分析功能变化的原因,看看是否能分离成不同的维度,然后通过桥接模式来分离它们,从而减少子类的数目。
六、桥接模式的特点
● 分离抽象和实现部分
桥接模式分离了抽象部分和实现部分,从而极大地提高了系统的灵活性。让抽象部分和实现部分独立开来,分别定义接口,这有助于对系统进行分层,从而产生更好的结构化的系统。对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了。
● 更好的扩展性
由于桥接模式把抽象部分和实现部分分离开了,而且分别定义接口,这就使得抽象部分和实现部分可以分别独立地扩展,而不会相互影响,从而大大地提高了系统的可扩展性。
● 可动态地切换实现
桥接模式把抽象部分和实现部分分离开了,所以在实现桥接的时候,就可以实现动态地选择和使用具体的实现。也就是说一个实现不再是固定地绑定在一个抽象接口上了,可以实现运行期间动态地切换。
● 可减少子类的个数
对于有两个变化维度的情况,如果采用继承的实现方式,大约需要两个维度上的可变化数量的乘积个子类;而采用桥接模式来实现,大约需要两个维度上的可变化数量的和个子类。可以明显地减少子类的个数。
七、总结
桥接模式很好地实现了面向对象设计中的开闭原则和多用对象组合、少用对象继承原则,它的本质是:分离抽象和实现。只有把抽象部分和实现部分分离开了,才能够让它们独立地变化;只有抽象部分和实现部分可以独立地变化,系统才会有更好的可扩展性和可维护性。