桥接模式也叫桥梁模式,是一种结构型设计模式。既然叫做桥梁,那么必然是起到连接两端的作用,那么在设计模式领域,这两端是哪两端呢?答案是:抽象与实现。但是在具体使用过程中,不一定严格按照这种方式来进行的。抽象部分也会有实体类,实现部分也会有抽象接口。下面来详细看一下。
定义
将抽象部分与实现部分分离,使他们可以独立的进行变化。
场景
- 涉及多维度的对象,每个维度需要实现自我变化
- 代替继承关系,来实现抽象角色和实现对象的独立运作
角色
抽象部分
保持一个对实现类的引用,在抽象方法中调用实现类的方法来完成行为,一般为抽象类
抽象部分优化类(抽象部分的扩展实现)
对抽象部分的实现,可以独立拥有自己的特征
实现部分
定义具体实现方法的抽象类,一般为接口或者抽象类,是提供实现部分进行自我变化的基础
具体实现部分
实现或者继承实现部分抽象类的类,一般是提供具体的业务实现方法
关于桥接两端的说明
从上面设计模式定义、角色说明来看,对于抽象和实现的定义可能会有点拗口。需要说明的是,这里所说的抽象和实现其实是两个可以独立变化的维度,并非是代码层次的接口抽象和类实现。桥接模式解决的是多维度环境下,各维度进行自我扩展的问题
代码演示
相信很多人都喝过咖啡,咖啡分很多种,有香草拿铁、榛果拿铁等,每份拿铁咖啡又分为全糖、半糖、无糖。我们分析一下,就容易发现这里面实际存在着两个可以自主变化的维度,香草、榛果属于咖啡种类,加多少糖属于添加品分量。就这个场景,我们用桥接模式来实现一下。
首先确定抽象和实现。上面说过,抽象和实现只是单纯的两个维度,因此咖啡种类和加糖粉量都可以作为抽象部分,也都可以作为实现部分。考虑到每种咖啡可能不仅仅存在加糖粉量这种变化,我们选择了咖啡种类来作为抽象部分。
我们先定义实现部分的接口
/**
* 添加糖量
* 实现部分接口
*/
public interface Sugar {
float NO_SUGAR = 0f;
float HALF_SUGAR = 0.5f;
float HOLE_SUGAR = 1.0f;
/**
* 返回添加糖的分量
*/
float sugarQuantity();
}
然后定义抽象部分的抽象类
/**
* 咖啡抽象类
* 持有一个加糖量的引用
*/
public abstract class Coffee {
protected Sugar mSugar;
public Coffee(Sugar sugar) {
this.mSugar = sugar;
}
/**
* 业务方法
*/
public abstract void coffeeType();
}
然后,我们需要根据具体的咖啡种类和加糖量来创建具体的实现类。
咖啡种类,分为香草拿铁和榛果拿铁
/**
* 香草拿铁
*/
public class VanillaLatte extends Coffee {
public VanillaLatte(Sugar sugar) {
super(sugar);
}
@Override public void coffeeType() {
System.out.println("香草拿铁 加糖量:" + mSugar.sugarQuantity());
}
}
/**
* 榛果拿铁
*/
public class HazelnutLatte extends Coffee {
public HazelnutLatte(Sugar sugar) {
super(sugar);
}
@Override public void coffeeType() {
System.out.println("榛果拿铁 加糖量:" + mSugar.sugarQuantity());
}
}
加糖量,分为全糖、半糖、不加糖
/**
* 全糖咖啡
*/
public class HoleSugar implements Sugar {
@Override public float sugarQuantity() {
return HOLE_SUGAR;
}
}
/**
* 半糖咖啡
*/
public class HalfSugar implements Sugar {
@Override public float sugarQuantity() {
return HALF_SUGAR;
}
}
/**
* 无糖咖啡
*/
public class NoSugar implements Sugar {
@Override public float sugarQuantity() {
return NO_SUGAR;
}
}
根据以上实现类,我们通过桥接就可以生产6种咖啡了。
public static void main(String[] args) {
// 全糖
HoleSugar holeSugar = new HoleSugar();
// 半糖
HalfSugar halfSugar = new HalfSugar();
// 无糖
NoSugar noSugar = new NoSugar();
// 香草拿铁全糖
VanillaLatte vanillaLatteWithHoleSugar = new VanillaLatte(holeSugar);
vanillaLatteWithHoleSugar.coffeeType();
// 香草拿铁半糖
VanillaLatte vanillaLatteWithHalfSugar = new VanillaLatte(halfSugar);
vanillaLatteWithHalfSugar.coffeeType();
// 香草拿铁无糖
VanillaLatte vanillaLatteWithNoSugar = new VanillaLatte(noSugar);
vanillaLatteWithNoSugar.coffeeType();
// 榛果拿铁全糖
HazelnutLatte hazelnutLatteWithHoleSugar = new HazelnutLatte(holeSugar);
hazelnutLatteWithHoleSugar.coffeeType();
// 榛果拿铁半糖
HazelnutLatte hazelnutLatteWithHalfSugar = new HazelnutLatte(halfSugar);
hazelnutLatteWithHalfSugar.coffeeType();
// 榛果拿铁无糖
HazelnutLatte hazelnutLatteWithNoSugar = new HazelnutLatte(noSugar);
hazelnutLatteWithNoSugar.coffeeType();
}
我们来看下输出
香草拿铁 加糖量:1.0
香草拿铁 加糖量:0.5
香草拿铁 加糖量:0.0
榛果拿铁 加糖量:1.0
榛果拿铁 加糖量:0.5
榛果拿铁 加糖量:0.0
上述示例中可以看到,咖啡种类和加糖量两个维度的属性可以自由变化而不受两个维度的任何对象的影响。通过组合甚至可以构建出更多的对象模型。
总结
桥接模式在开发工作中可能使用的不多,主要原因是对抽象和实现部分的把握,哪一部分应该作为抽象部分,哪一部分应该作为实现部分,甚至是否有必要进行分离?这些对于一个设计者来说,都需要根据系统的特性来决断,当然这也是桥接模式使用过程比较困难的一点。但是不能否认,桥接模式对于抽象和实现的分离,使两端可以灵活扩展的优点也是显而易见的。