设计模式之桥接模式
定义
将抽象部分和它的实现部分分离,使它们都可以独立地变化。在现实生活中,某些类有两个或多个维度的变化,
如图形既可以按形状分,又可以按颜色分,如何画不同形状不同颜色的图形呢,如果用继承方式,m 种形状 n 种颜色的图形就有 m * n 种,
不但对应的子类很多,而且扩展困难,桥接模式就能很好的解决这些问题。
结构
- Abstraction,抽象部分接口,持有一个实现部分的对象。
- RefinedAbstraction,扩展抽象部分的对象,调用实现部分的对象来实现具体的业务方法。
- Implementor,实现部分接口,定义自己的方法供抽象部分调用。
- ConcreteImplementor,具体的实现部分对象。
简单实现
抽象部分接口
public abstract class Abstraction {
protected Implementor implementor;
public Abstraction(Implementor implementor) {
this.implementor = implementor;
}
abstract void operation();
}
扩展抽象部分
public class RefinedAbstraction extends Abstraction {
public RefinedAbstraction(Implementor implementor) {
super(implementor);
}
@Override
void operation() {
implementor.operation();
}
}
实现部分接口
public interface Implementor {
void operation();
}
具体实现部分对象
public class ConcreteImplementorA implements Implementor {
@Override
public void operation() {
System.out.println("ConcreteImplementorA operation()");
}
}
另一个具体实现对象
public class ConcreteImplementorB implements Implementor {
@Override
public void operation() {
System.out.println("ConcreteImplementorB operation()");
}
}
客户端
public class Client {
public static void main(String[] args) {
Abstraction abstraction = new RefinedAbstraction(new ConcreteImplementorA());
abstraction.operation();
}
}
消息系统
以一个发送各种消息的业务为例,消息按类型可以分为短信消息,邮件消息,按紧急程度可以分为普通消息,加急消息,整个消息系统可以划分为两个维度。
消息接口
public interface Msg {
/**
* 发送消息
*
* @param toUser 消息的接受者
* @param content 要发送消息的内容
*/
void send(String toUser, String content);
}
实现部分的接口
短信消息
public class SmsMsg implements Msg {
public void send(String toUser, String content) {
System.out.println("使用短信消息发送'" + content + "'给" + toUser);
}
}
邮件消息
public class EmailMsg implements Msg {
public void send(String toUser, String content) {
System.out.println("使用邮件消息发送'" + content + "'给" + toUser);
}
}
抽象消息类
/**
* 抽象消息类
*/
public abstract class AbstractMsg {
/**
* 持有实现部分的对象
*/
protected Msg msg;
public AbstractMsg(Msg msg) {
this.msg = msg;
}
public void sendMsg(String toUser, String content) {
this.msg.send(toUser, content);
}
}
抽象部分,内部持有一个实现部分的对象
普通消息
/**
* 普通消息类
*/
public class NormalMsg extends AbstractMsg {
public NormalMsg(Msg msg) {
super(msg);
}
@Override
public void sendMsg(String toUser, String content) {
//对于普通消息,直接调用父类方法发送消息即可
super.sendMsg(toUser, content);
}
}
加急消息
/**
* 加急消息类
*/
public class UrgencyMsg extends AbstractMsg {
public UrgencyMsg(Msg msg) {
super(msg);
}
@Override
public void sendMsg(String toUser, String content) {
content = "【加急】" + content;
super.sendMsg(toUser, content);
}
//扩展功能,监控某个消息的处理状态
public Object watch(String msgId) {
//根据给出的消息编码(msgId)查询消息的处理状态
return null;
}
}
客户端
public class Client {
public static void main(String[] args) {
//发送普通的对象消息
Msg msg = new SmsMsg();
AbstractMsg abstractMsg = new NormalMsg(msg);
abstractMsg.sendMsg("李总", "加班申请速批");
//发送加急的邮件消息
msg = new EmailMsg();
abstractMsg = new UrgencyMsg(msg);
abstractMsg.sendMsg("李总", "加班申请速批");
}
}
输出结果为
使用短信消息发送'加班申请速批'给李总
使用邮件消息发送'【加急】加班申请速批'给李总
在上述示例中,我们使用桥接模式解耦了"消息类型"和"消息紧急程度"这个两个独立变化的维度。
后续如果想扩展消息类型如微信消息、钉钉消息等,新建一个类实现Msg接口即可,如果想要扩展紧急程度,
新建一个类继承AbstractMsg就可以了。
桥接模式在JDK中的实现
JDK中的JDBC结构
应用程序依赖于左边的JDBC的API之上,不依赖具体的数据库驱动,两边都可以独立的进行扩展。
总结
优点
- 分离抽象和实现部分,提高了系统的灵活性和可扩展性。
- 可以在运行时动态地切换实现。
缺点
- 要求正确识别出系统中两个独立变化的维度(抽象和实现),增加了系统的设计难度。
本质
桥接模式的本质是分离抽象和实现。分离之后才能独立的变化,才有更好的可扩展性。
广义上的桥接
Java中一个很重要的编程原则就是"面向接口编程",也可以叫做"面向抽象编程",使用接口的程序结构图如下
如果将桥接模式的抽象部分简化一下,暂时不要 RefinedAbstraction 部分,那么就和上图的结构差不多了。
从某个角度来讲,桥接模式就是"面向抽象编程"设计原则的扩展,通过具体实现的接口将抽象部分和具体的实现部分分离开来,
抽象部分相当于是使用实现部分接口的客户程序,这样一来,几乎可以把所有面向抽象编程的程序,都视作桥接模式的体现,
至少也是简化的桥接模式,就算是广义的桥接吧。而Java编程很强调"面向抽象编程",因此,广义的桥接,在Java中可以说是无处不在。
使用场景
- 一个类存在两个或多个独立变化的维度,且多个维度都需要独立的进行扩展。
- 希望在抽象和实现之间增加更多的灵活性,动态切换具体的实现。
参考
大战设计模式【16】—— 桥接模式
大战设计模式(第二季)【7】———— 从源码看桥接模式
设计模式的征途—8.桥接(Bridge)模式
设计模式(八)——桥接模式
《JAVA设计模式》之桥接模式(Bridge)
桥接模式(Bridge模式)详解
研磨设计模式-书籍