桥接模式介绍
桥接模式(Bridge Pattern)也称为桥梁模式,是结构型设计模式之一。
桥接模式的定义
将抽象部分与实现部分分离,使它们都可以独立地进行变化。
桥接模式的使用场景
- 从模式的定义中我们大致可以了解到,这里“桥梁”的作用其实就是连接“抽象部分”与“实现部分”,但是事实上,任何多维度变化类或者说多个树状类之间的耦合都可以使用桥接模式来实现解耦。
- 如果一个系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,可以通过桥接模式使它们在抽象层建立一个关联关系。
- 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,也可以考虑使用桥接模式。
- 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
桥接模式的UML类图
UML类图如下:
根据类图我们可以得出如下一个桥接模式的通用模式代码: 实现部分的抽象接口
package com.guifa.observerdemo;
public interface Implementor {
/**
* 实现抽象部分的具体方法
*/
public void operationImpl();
}
实现部分具体的实现一
package com.guifa.observerdemo;
public class ConcreteImplementorA implements Implementor {
@Override
public void operationImpl() {
// 具体实现
}
}
实现部分具体的实现二
package com.guifa.observerdemo;
public class ConcreteImplementorB implements Implementor {
@Override
public void operationImpl() {
// 具体实现
}
}
抽象部分的实现
package com.guifa.observerdemo;
public abstract class Abstraction {
// 声明一个私有成员变量引用实现部分的对象
private Implementor implementor;
/**
* 通过实现部分对象的引用构造抽象部分的对象
*
* @param implementor implementor
*/
public Abstraction(Implementor implementor) {
this.implementor = implementor;
}
/**
* 通过调用实现部分具体的方法实现具体的功能
*/
public void operation() {
implementor.operationImpl();
}
}
抽象部分的子类
package com.guifa.observerdemo;
public class RefinedAbstraction extends Abstraction {
/**
* 通过实现部分对象的引用构造抽象部分的对象
*
* @param implementor implementor
*/
public RefinedAbstraction(Implementor implementor) {
super(implementor);
}
/**
* 对父类抽象部分中的方法进行扩展
*/
public void refinedOperation() {
// 对中的方法进行扩展
}
}
客户类
package com.guifa.observerdemo;
public class Client {
public static void main(String[] args) {
// 客户调用逻辑
}
}
角色介绍:
- Abstraction:抽象部分。
该类保持一个对实现部分对象的引用,抽象部分中的方法需要调用实现部分的对象来实现,该类一般为抽象类。 - RefinedAbstraction:优化的抽象部分。
抽象部分的具体实现,该类一般是对抽象部分的方法进行完善和扩展。 - Implementor:实现部分。
可以为接口或者抽象类,其方法不一定要与抽象部分中的一致,一般情况下是由实现部分提供基本的操作,而抽象部分定义的则是基于实现部分这些基本操作的业务方法。 - ConcreteImplementorA/ConcreteImplementorA:实现部分的具体实现。
完善实现部分中方法定义的具体逻辑。 - Client:客户类,客户端程序。
桥接模式的简单实现
以喝咖啡为例,简单分为四种类型:大杯加糖、大杯不加糖、小杯加糖、小杯不加糖,先定义一个咖啡类:
package com.guifa.observerdemo;
public abstract class Coffee {
public CoffeeAdditives coffeeAdditives;
public Coffee(CoffeeAdditives coffeeAdditives) {
this.coffeeAdditives = coffeeAdditives;
}
/**
* 咖啡具体什么样子由子类决定
*/
public abstract void makeCoffee();
}
Coffee类中保持了对CoffeeAdditives的引用,以便调用具体的实现。同样地,咖啡还分大杯小杯,定义两个子类继承于Coffee。
package com.guifa.observerdemo;
public class LargeCoffee extends Coffee {
public LargeCoffee(CoffeeAdditives coffeeAdditives) {
super(coffeeAdditives);
}
@Override
public void makeCoffee() {
System.out.println("大杯的" + coffeeAdditives + "咖啡");
}
}
package com.guifa.observerdemo;
public class SmallCoffee extends Coffee {
public SmallCoffee(CoffeeAdditives coffeeAdditives) {
super(coffeeAdditives);
}
@Override
public void makeCoffee() {
System.out.println("小杯的" + coffeeAdditives + "咖啡");
}
}
而对于加进咖啡中糖,当然也可以不加,我们也用一个抽象类定义。
package com.guifa.observerdemo;
public abstract class CoffeeAdditives {
/**
* 具体要往咖啡里加什么也是由子类实现
*
* @return 具体加的东西
*/
public abstract String addSomething();
}
CoffeeAdditives对应的两个子类:加糖与不加糖:
package com.guifa.observerdemo;
public class Sugar extends CoffeeAdditives {
@Override
public String addSomething() {
return "加糖";
}
}
package com.guifa.observerdemo;
public class Ordinary extends CoffeeAdditives {
@Override
public String addSomething() {
return "原味";
}
}
不加糖我们以原味表示,最后来看客户类,将两者进行整合。
package com.guifa.observerdemo;
public class Main {
public static void main(String[] args) {
// 原味
Ordinary implOrdinary = new Ordinary();
// 糖
Sugar implSugar = new Sugar();
// 大杯咖啡 原味
LargeCoffee largeCoffeeOrdinary = new LargeCoffee(implOrdinary);
largeCoffeeOrdinary.makeCoffee();
// 小杯咖啡 原味
SmallCoffee smallCoffeeOrdinary = new SmallCoffee(implOrdinary);
smallCoffeeOrdinary.makeCoffee();
// 大杯咖啡 加糖
LargeCoffee largeCoffeeSugar = new LargeCoffee(implSugar);
largeCoffeeSugar.makeCoffee();
// 小杯咖啡 加糖
SmallCoffee smallCoffeeSugar = new SmallCoffee(implSugar);
smallCoffeeSugar.makeCoffee();
}
}
若此时咖啡厅为满足更多人的习惯,推出中杯咖啡怎么办?这里我们定义中杯扩展Coffee类即可。
package com.guifa.observerdemo;
public class MiddleCoffee extends Coffee {
public MiddleCoffee(CoffeeAdditives coffeeAdditives) {
super(coffeeAdditives);
}
@Override
public void makeCoffee() {
System.out.println("中杯的" + coffeeAdditives + "咖啡");
}
}
对应的客户类做出相应的增加即可。
// 中杯咖啡 原味
MiddleCoffee middleCoffeeOrdinary = new MiddleCoffee(implOrdinary);
largeCoffeeSugar.makeCoffee();
// 中杯咖啡 加糖
MiddleCoffee middleCoffeeSugar = new MiddleCoffee(implSugar);
smallCoffeeSugar.makeCoffee();
同样地,为了增加咖啡口味的种类,我们也可以让CoffeeAdditives类变化起来,增加更多的子类表示即可。
总结
桥接模式可以应用到许多开发中,但是它应用的却不多,一个很重要的原因是对于抽象与实现的分离的把握,是不是需要分离,如何分离?对于设计者来说要有一个恰到好处的分寸。不管怎么说,桥接模式的优点我们毋庸置疑,分离抽象与实现、灵活的扩展以及对客户来说透明的实现等。但是使用桥接模式也有一个不明显的缺点,上面我们也提到类,就是不容易设计,对开发者来说要有一定的经验要求。因此,对桥接模式应用来说,理解很简单,设计却不容易。