|
一、桥接模式的本质和目的
桥接模式的本质是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展,基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。
在这儿不理解没关系,通过后面的例子就可以深刻理解了。
二、示例场景
一个咖啡店卖咖啡,卖出的咖啡可以从几个角度来看:
1. 口味:摩卡、拿铁、美式等;
2. 容量:大杯、中杯、小杯等;
3. 添加物:牛奶、糖、蜂蜜等;
4. 温度:热、常温、冷等;
如果需要设计一个点餐系统,来生成咖啡订单,如何在系统中将上面说到的拥有4个属性的咖啡生成呢?
第一种方法
为每种咖啡组合编写不同的类,如下
1. 大杯热牛奶摩卡类;
2. 中杯热牛奶摩卡类;
3. 小杯热牛奶摩卡类;
4. 大杯热蜂蜜摩卡类; ......
试想一下,需要多少各类?假设4个维度分别可选的值是a、b、c、d,那么需要定义的类就是a x b x c x d种。而且每种属性都是固化到特定的类,没法复用,按照这个方法要定义3x3x3x3=81个类。
第二种方法
针对第一种方法,我们能不能单独定义每种属性,并让每种属性能够复用,然后将他们组合/聚合起来形成一杯咖啡呢?这样就减少每个属性的重复定义了。 例如: 1. 口味:摩卡、拿铁、美式等分别定义一个类;3个类 2. 容量:大杯、中杯、小杯等分别定义一个类;3个类 3. 添加物:牛奶、糖、蜂蜜等分别定义一个类;3个类 4. 温度:热、常温、冷等分别定义一个类;3个类
如果能够找到一种方法把这4个维度组合起来,是不是总的类数就变成a+b+c+d=3+3+3+3=12个类?类的数量锐减。
那么如何将这个4个维度组合起来实现一杯咖啡呢?
三、桥接模式的原理
桥接模式就是为了实现上面的第二种方法的,先进行抽象,然后通过桥接将属性连接起来。
看一下下面原理图(初步思路):

桥接模式思想示意图
这个思路是否可以再优化一下?
进一步思路如下图:

桥接模式思想优化示意图
想一下,咖啡的最重要的属性就是口味(也可以说是本质的属性),其它的属性相对来说不是最重要的,那么我们就让口味直接通过继承的方式实现属性的集成,其它三个属性为了提升替换性/重用性,可以也进行一个抽象类的定义,然后去具体实现。
这样设计后,通过组合/聚合实现了咖啡多个属性的集成,减少了类的数量。
图中组合/聚合关系就是桥接模式中Bridge的核心要义,通过组合/聚合将多个属性连接起来的。
最后就形成了桥接模式的通用示意图,如下:

桥接模式UML示意图
桥接模式的角色: * 抽象化(Abstraction)角色:抽象化给出的定义,并保存一个对实现化对象的引用,就是上面所说的Coffe抽象类。
- 修正抽象化(RefinedAbstraction)角色:扩展抽象化角色,改变和修正父类对抽象化的定义,就是上面的口味实现类。
- 实现化(Implementor)角色:这个角色给出实现化角色的接口,但不给出具体的实现。必须指出的是,这个接口不一定和抽象化角色的接口定义相同,实际上,这两个接口可以非常不一样。实现化角色应当只给出底层操作,而抽象化角色应当只给出基于底层操作的更高一层的操作。就是上面的容量、温度、添加物抽象类。
- 具体实现化(ConcreteImplementor)角色:这个角色给出实现化角色接口的具体实现,就是上面的容量、温度、添加物实现类。

桥接模式UML示意图(与实例映射版)
四、 代码实现与分析
结合上面的示例场景和原理,我们开始实现代码。
1. 抽象化(Abstraction)角色:Coffe抽象类-AbstractCoffe类的定义
public abstract class AbstractCoffe {
// 重点:此处就是连接其它属性的关键,通过成员变量的方式聚合了其它属性
ICapacity capacity;
IAdditives additives;
ITemperature temperature;
// 此处通过有参构造的方式接受客户端想要的咖啡属性
public AbstractCoffe(ICapacity capacity,IAdditives additives,ITemperature temperature){
this.capacity = capacity;
this.additives = additives;
this.temperature = temperature;
}
public abstract void Order(int count);
}2. 修正抽象化(RefinedAbstraction)角色:口味实现类-MocaCoffe类定义
public class MocaCoffe extends AbstractCoffe{
public MocaCoffe(ICapacity capacity,IAdditives additives,ITemperature temperature){
super(capacity, additives, temperature);
}
public void Order(int count) {
System.out.println("开始制作摩卡咖啡:");
capacity.SetCupCapacity();
additives.AddAdditives();
temperature.SetTemperature();
System.out.println(count+" 杯Moca Coffe制作完成!");
}
}本例中就实现了一种类型,就是摩卡,还可以实现其它的口味类型,代码类似,此处就省略了。
3. 实现化(Implementor)角色:容量、温度、添加物抽象类-ICapacity、ITemperature、IAdditives抽象类定义
(1)容量抽象类ICapacity
public interface ICapacity {
public void SetCupCapacity();
}(2)温度抽象类ITemperature
public interface ITemperature {
public void SetTemperature();
}(3)添加物抽象类IAdditives
public interface IAdditives {
public void AddAdditives();
}4. 具体实现化(ConcreteImplementor)角色:容量、温度、添加物实现类-Capacity、Temperature、Additives抽象类定义
(1)Capacity实现类-3种
// 大杯
public class LargeCapacity implements ICapacity{
@Override
public void SetCupCapacity() {
System.out.println("制作杯子尺寸:Large");
}
}
//中杯
public class MiddleCapacity implements ICapacity{
@Override
public void SetCupCapacity() {
System.out.println("制作杯子尺寸:Middle");
}
}
//小杯
public class SmallCapacity implements ICapacity{
@Override
public void SetCupCapacity() {
System.out.println("制作杯子尺寸:Small");
}
}(2)Temperature实现类-3种
// 热饮
public class HotTemperature implements ITemperature{
@Override
public void SetTemperature() {
System.out.println("加热温度至:Hot");
}
}
// 冷饮
public class NormalTemperature implements ITemperature{
@Override
public void SetTemperature() {
System.out.println("加热温度至:Normal");
}
}
// 常温
public class ColdTemperature implements ITemperature{
@Override
public void SetTemperature() {
System.out.println("加热温度至:Cold");
}
}(3)Additives实现类-2种
// 加奶
public class MilkAdditives implements IAdditives{
@Override
public void AddAdditives() {
System.out.println("添加 MILK 成功!");
}
}
// 加糖
public class SugarAdditives implements IAdditives{
@Override
public void AddAdditives() {
System.out.println("添加 SUGAR 成功!");
}
}5. 客户端类定义
public class Main {
public static void main(String[] args) {
// 客户端传入想要的咖啡各个属性的具体对象
AbstractCoffe mocaCoffe = new MocaCoffe(new MiddleCapacity(),new SugarAdditives(),new ColdTemperature());
mocaCoffe.Order(4);
}
}五、桥接模式的实现关键点
桥接模式实现需要注意一下几点:
- 抽象化角色提取,就是所有属性中找到最本质的那个属性,例如咖啡最本质的属性就是口味。其实,这个有没有都没关系,可以将所有属性都作为实现化(Implementor)角色;如下面所示:
public class Coffe{
ICapacity capacity;
IAdditives additives;
ITemperature temperature;
ITaste taste;
public Coffe(ICapacity capacity, IAdditives additives, ITemperature temperature,ITaste taste) {
this.capacity = capacity;
this.additives = additives;
this.temperature = temperature;
this.taste = taste;
}
public void Order(int count) {
taste.makeCoffeTaste();
capacity.SetCupCapacity();
additives.AddAdditives();
temperature.SetTemperature();
System.out.println(count+" 杯Coffe制作完成!");
}
}
// 这个例子中就去掉了抽象化角色,体会一下。只不过扩展下就会降低。
- 对属性进行抽象,需要能够识别某个对象更高一级的属性,例如将大杯、中杯、小杯识别为容量这个抽象概念;
- 设计桥接方法,需要将抽象后的属性连接进咖啡实现类,就是上面例子中的成员变量和构造函数的用途;
六、桥接模式的优缺点
优点
- 抽象与实现分离,扩展能力强,符合开闭原则。
- 实现细节对客户透明。
- 减少了因为继承带来的类爆炸。
缺点
适用场景
- 某个类有多个的维度的变化,如果用继承就会使项目变的臃肿,会产生许多的子类。
- 抽象的部分和实现的部分都应该可以扩展。
<< 10 设计模式自学笔记(Java)-装饰器模式Decorator[结构型模式]
>> 12 设计模式自学笔记(Java)-组合模式Composite[结构型模式] |
|