定义
将抽象部分与它的实现部分解藕,使它们都可以独立地变化。
小故事
前不久,我入职了一家奇葩公司并为他们开发了一款绘制几何图形的软件,它可以绘制4种图形,分别是红色的圆、蓝色的圆、红色的正方形、蓝色的正方形。
当时,实现需求我用的方式是继承:首先创建一个图形接口并定义一个draw方法,然后创建四个实现类并实现各自的逻辑。
今天,产品经理和我说要新增10种几何图形,其中每种图形都要有5个颜色。
我心里估算了一下大概需要新增50个类,便告诉产品经理一周能搞定。他没说啥,看样子是不开心了。
快下班时,我收到了老板发我的一份离职申请单,理由是我能力无法胜任架构岗位。
太扯了,我就一初级程序员!
问题
言归正传,我们可以把图形的绘制流程分为"绘制形状"和"填充颜色"这两个步骤。
如果我还是用"继承的方式"实现产品经理的需求,那么我们可以体验到下面的两个问题。
代码重复:在绘制流程中,同类型不同颜色的图形在"绘制形状"这一步的代码是重复的,相同颜色不同种类的图形在"填充颜色"这一步的代码是重复的。
"关联扩展":当需要要增加一种颜色时,有多少种图形就要增加多少种实现;同样的,当增加一种图形时,有多少种颜色,就要增加多少种实现。
之所以如此,是因为"绘制形状"和"填充颜色"这两个步骤是一种组合关系,而我采用的是"列举式"的方式这就会使两个操作耦合在一起,所以出现了上面的问题。
方案
既然我们知道draw方法中两个步骤"绘制形状"和"填充颜色"是组合关系,那么我们首先就应该将这两者分离:为形状建立一个独立的继承等级结构,也为颜色建立一个独立的等级结构,
两个等级结构中的实现类都只负各自的步骤。
然后,让颜色等级结构中的抽象类持有一个指向形状等级结构中的抽象类的引用。最后,让颜色等级结构中的实现类先调用图形实现类的绘制方法再为图形填充颜色。
这样,如果我们要为所有形状新增颜色,只需要向颜色等级结构中增加对应的实现类;如果我们要新增一个支持所有颜色的图形,那么只需要向形状等级结构中增加对应的实现类。
我们把面向客户填充颜色这一层称为抽象层,把被颜色这一层关联的绘制图形这一层称为实现成,那么桥接模式的定义:将抽象部分与它的实现部分解藕使它们都可以独立地变化,我们就能理解了。
注!这里的抽象部分和实现部分不是指具体的类而是指一个整体中的抽象层和实现层,抽象层和实现层的关系就如同方向盘和车轮、遥控和电视机、手柄和游戏机之间的关系。
结构
抽象(Abstraction):该抽象类是抽象层接口,持有一个指向Implementor类型的对象的引用。
修正抽象(RefinedAbstraction):抽象层中的具体实现类,它接收客户请求并实现其中一个维度的逻辑,然后把请求委派给实现层中的具体实现类。
实现(Implementor):该接口是实现层的接口,它和抽象层接口在修正抽象类和具体实现类之间起桥梁的作用。
具体实现(ConcreteImplementor):实现层中的具体实现类。
下面是UML结构图对应的代码:
//客户
public class Client {
public static void main(String[] args){
Implementor implementor=new ImplementorImpl();
//将分离后的抽象类和实现类进行组合
RefinedAbstraction abstraction = new RefinedAbstraction(implementor);
//客户只和抽象层交互
abstraction.operation();
}
}
//抽象
public abstract class Abstraction {
//指向具体实现的引用对象
protected Implementor implementor;
public Abstraction(Implementor implementor){
this.implementor=implementor;
}
public abstract void operation();
}
//修正抽象
public class RefinedAbstraction extends Abstraction{
public RefinedAbstraction(Implementor implementor) {
super(implementor);
}
@Override
public void operation() {
//进行逻辑控制
//然后委派实现类进行处理
super.implementor.operation();
}
}
//实现
public interface Implementor {
//将实现层抽象化
public void operation();
}
//具体实现
public class ImplementorImpl implements Implementor{
@Override
public void operation() {
//实现
}
}
应用
假设,我们已经为运营团队开发了一套消息通知系统用于各种营销活动通知。它目前已经支持短信和邮件两种类型的通知,以及按天或按周进行短信发送频次控制。如下:
我们已经知道,这样的继承等级结构不适合进行两个维度的扩展,消息类型和控制类型。所以,我们得重构一下:将抽象层和实现层解藕,让它们可以独立变化而不相互影响。
首先,分离实现层和抽象层并让实现层拥有独立的继承等级结构。
//实现层独立的接口
public interface MessageSender {
public void send(Message message);
}
//重构原来SMS的实现,让其继承新接口
public class SMSMessageSender implements MessageSender{
@Override
public void send(Message message) {
System.out.println("发送SMS消息");
}
}
//重构原来Email的实现,让其继承新接口
public class EmailMessageSender implements MessageSender{
@Override
public void send(Message message) {
System.out.println("发送Email消息");
}
}
其次,也让抽象层拥有一个独立的继承等级结构,并将其与实现成关联起来。
//消息通知抽象类(重构之前是接口)
public abstract class Notifier {
//通过引用关联抽象层对象
protected MessageSender messageSender;
public Notifier(MessageSender messageSender){
this.messageSender = messageSender;
}
abstract public void send(Message message);
}
//抽象层-按天频次控制实现类(重构之前是抽象类)
public class ControlByDayNotifier extends Notifier{
public ControlByDayNotifier(MessageSender messageSender) {
super(messageSender);
}
@Override
public void send(Message message) {
if(今日已发送数量<=今日允许最大发送数量){
//委托实现层对象发送消息
super.messageSender.send(message);
}
}
}
//抽象层-按周频次控制实现类(重构之前是抽象类)
public class ControlByWeekNotifier extends Notifier{
public ControlByWeekNotifier(MessageSender messageSender) {
super(messageSender);
}
@Override
public void send(Message message) {
if(本周已发送数量<=本周允许最大发送数量){
//委托实现层对象发送消息
super.messageSender.send(message);
}
}
}
最后,我们模拟客户端发送短信并且按天进行频次控制。
public class Client {
public static void main(String[] args){
SMSMessageSender sender =new SMSMessageSender();
//组合实现层对象
ControlByDayNotifier notifier = new ControlByDayNotifier(sender);
Message message = new Message();
//客户只和抽象层进行交互
notifier.send(message);
}
}
总结
当一个对象的某个操作的实现流程由多个独立变化的步骤组合而成时,可以使用桥接模式解藕步骤之间的静态关系,这样可以使你在扩展其中一个步骤时不依赖另一个步骤。