一、桥接模式的概念
桥接模式是将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体[Handle and Body]模式或接口[Interfce]模式。
听懂了这句话就不用往下看了,说明你会了。
听不懂我觉得也正常,如果用一句话能学会就没人看书了。像我这种笨人,都是学会了一个模式,然后往它的定义上套。
二、什么时候使用桥接模式
上面的概念中说到,抽象部分与它的实现(功能)部分分离,使它们都可以独立地变化。看下面这句话:
类的层级结构只有一层,功能层次结构与实现层级结构是混杂在一个层级结构中时,你可以使用桥接模式。 —— 《图解设计模式》
在说之前先说三个概念:
- 层的层级结构层数
- 功能层次结构
- 实现层级结构
可能名词比较陌生,我刚看的时候觉得好高大上啊,没听过的技术名词都觉得高大上。
但这三个名词用白话来描述就特别简单。
2.1类的层级结构的层数
用一句话就能说明白。
类的层级结构就是类与子类之间的继承的层数。
这个图中类的层级结构的层数是:1
这个图中类的层级结构的层数是:2
类的层级结构的层数不能太深。
2.2 功能层次结构
类的功能层次结构,用一句话说不完,得两句话。
首先两个类有继承关系。
父类具有一些基本功能,在子类中添加了新的功能,这就叫功能层次结构。
还是不太好理解,我当时是这么理解的,子类继承了父类的方法之外,还有自己的方法,这种层次结构叫功能层次结构。例如下面的图:
动物定义了呼吸方法,然后狗继承了动物,狗还加了一个犬吠方法,这种层次结构就叫功能层次结构。
2.3 实现层次结构
类的实现层次结构,也得用两句话说明。
首先两个类也有继承关系。
父类通过抽象的方式来定义方法。然后子类通过实现父类的抽象方法来完成这个方法的具体实现。
简单说就是一个类实现了父类的抽象方法。
动物定义了一个移动的抽象方法,狗实现了这个抽象方法,这种层次结构就叫实现层次结构。
2.4 总结
类的层级结构只有一层,功能层次结构与实现层级结构混杂在一个层级结构中,这样很容易让类的设计变得复杂,也难以透彻地理解类的层次结构。因为自己也难以确定究竟在类的哪一个层次结构去增加新需求。
这个时候,可以尝试使用桥接模式,将抽象部分与它的实现部分分离,使它们都可以独立地变化。
三、怎么使用桥接模式
简单说,就是怎么做抽象部分与它的实现部分分离。
下面会以一个场景来说明,主要是两件事:
- 这个场景下不使用桥接模式会带来什么弊端;
- 怎么使用桥接模式来解决这个弊端;
先看来图来说明:
类图以奔驰车为例做了一个模型,奔驰的车有不同的车系,每个车系都可以远程开门,而车系下的各个车的协议解析都不同。所以在车系中定义了远程开关车门的抽象方法。由各个开门协议对象去实现自己的协议解析。
这种方法目前看没有什么问题,但是设计原则也好,设计模式也好,或者说设计工作,一个很重要任务就是要考虑到未来的需求变更。
那么以这个图作为假设,需求变化成还有Benz的B、D、E系列的车,每个下面有2个具体车型,那么我们就要加入3个抽象类,6个实现类,就好比下面的类图:
3.1 特定场景下不使用桥接模式带来的弊端
类爆炸
首先,上面的场景,从类的实现层次结构来讲,每次实现的增加都成倍的增加类,最后会出现类爆炸的场面,就如上图所示。
相同的例子还有很多,比如有用画国画举例的,画国画需要毛笔,要用到小号、中号、大号的毛笔,另外还需要水彩(12颜色)。组合起来是:颜色数量 * 毛笔型号数量 = 12 * 3 = 36,需要36(红色小号、红色中号、红色大号,绿色小号 …… 黑色大号)个类。如果毛笔的型号需要5种,颜色需要24中,那么需要120个类。
难扩展
其次,上面的场景,想要实现一些新的功能,难以决定加到哪一层级。
例如:某些车要加一些新的功能,比如全部开门、前排开门、后排开门等……难以确定要加到哪一层,比如要在C系列加一个全部开门,但C400没有这个功能,而C200、C300、C500有,如果你在父类加入那么C400也会有这个功能,如果在C200、C300、C500上加入,那代码的重复率又太高。
再例如:想要加入开窗功能,那么又要创建额外超多的开窗协议实现类。
3.2 如何使用桥接模式解决这个问题
经过桥接模式重构,得到上面的类图,将协议与车型分开,通过聚合的方式将车型与协议联接在一起,起到的重点是,分开后更容易扩展。当要增加新功能时,在类的功能层次结构(左侧,车型侧)增加即可,不必对类的实现层次结构(右侧,协议侧)进行修改。反之亦然。
上面的例子中,如果新增了A系列车的具体车型,不需要增加协议类,如果增加了开门协议,也不需要增加车型类(B型车,C型车……同A型车)。
上一小节说到的国画需求也是一样,左侧可以是毛笔和3中型号的毛笔类,右侧是水彩和12种颜色的水彩类,加起来是15个类,添加毛笔型号不需要增加颜色一侧的类,添加颜色也不需要增加毛笔一次的类。
3.3 具体代码
测试Main方法,里面是桥接模式的使用方式
package cc.xuepeng;
/**
输出:
A系列开门协议解析。
奔驰-A100开门
A系列开门协议解析。
奔驰-A200开门
A系列开门协议解析。
奔驰-A100: 第1个门-开门。
奔驰-A100: 第2个门-开门。
奔驰-A100: 第3个门-开门。
奔驰-A100: 第4个门-开门。
*/
public class Client {
public static void main(String[] args) {
BenzA benzA100 = new BenzA100(new ProtocolBenzA(), "奔驰-A100");
BenzA benzA200 = new BenzA200(new ProtocolBenzA(), "奔驰-A200");
benzA100.openDoor();
benzA200.openDoor();
((BenzA100) benzA100).openAllDoor();
}
}
具体代码
// A系列奔驰车,类的功能结构层次的父类
public class BenzA {
protected Protocol protocol;
protected String name;
public BenzA(Protocol protocol, String name) {
this.protocol = protocol;
this.name = name;
}
public void openDoor() {
protocol.resolveOpenDoorProtocol();
System.out.println(name + "开门");
}
}
public class BenzA100 extends BenzA {
public BenzA100(Protocol protocol, String name) {
super(protocol, name);
}
public void openAllDoor() {
super.protocol.resolveOpenDoorProtocol();
for (int i = 1; i <= 4; i++) {
System.out.println(super.name + ": 第" + i + "个门-开门。");
}
}
}
public class BenzA200 extends BenzA {
public BenzA200(Protocol protocol, String name) {
super(protocol, name);
}
}
// A系列车的协议解析类,类的实现层次结构的父类
public abstract class Protocol {
public abstract void resolveOpenDoorProtocol();
}
public class ProtocolBenzA extends Protocol {
public void resolveOpenDoorProtocol() {
System.out.println("A系列开门协议解析。");
}
}
四、总结
桥接模式,从我个人角度来看,要求我们设计时先从业务的维度出发去思考,考虑后续需求变更是否会使类的结构变得更复杂。
如果业务复杂(类型的子类较多,实现功能也各有不同),并且后续的变更或者新功能会很频繁,那么就考虑使用桥接模式,将类的功能层次与类的实现层次拆分开,然后以包含(Has a)的方式建立功能层次与实现层侧的关系,即可在后续有需求变更时得到以下几点好处:
- 代码结构更清晰;
- 业务结构更明确;
- 减少类的数量;
以上就是我对桥接模式的一些理解,有不足之处请大家矫正,谢谢。
来源:oschina
链接:https://my.oschina.net/u/2450666/blog/3220845