一、制作计算器的外壳
如果要制作计算器的外壳,外壳类主要有两个属性,颜色和材质,不同颜色和材质的组合就是不同的外壳对象。例如颜色有三种变化:红、绿、黄,材质也有三种选择:塑料、金属、陶瓷,就可以组合出九种不同的计算器,如果每种计算器都需要实现其特定的功能,那么就需要九个子类来继承外壳类。
实际上,如果把颜色单独抽象出来作为一个对象,那么颜色的变化就不在外壳类的考虑范围内,外壳类只需扩展三种材质的子类即可,而让颜色类自己去扩展自己的子类。然后在客户端中自由组合外壳种类和颜色种类。
像这种要实现的对象、系统可能有多角度的分类,每一种分类都可能有很多种变化的情况,只用继承会增加大量的子类。就可使用桥接模式,把这种多角度分离出来让他们独立变化,减少他们之间的耦合。
二、桥接模式
桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。
实现是指抽象类和它的派生类用来实现自己的对象。由于实现的方式有多种,桥接模式的核心意图就是把这些实现独立出来,让他们各自地变化。这就使得每种实现的变化不会影响其他实现,从而达到应对变化的目的。
在使用桥接模式时,我们首先应该识别出一个类所具有的两个独立变化的维度,将它们设计为两个独立的继承等级结构,为两个维度都提供抽象层,并建立抽象耦合。
桥接是一个接口,它与一方应该是绑定的,也就是解耦的双方中的一方必然是继承这个接口的,这一方就是实现方,而另一方正是要与这一方解耦的抽象方,如果不采用桥接模式,一般我们的处理方式是直接使用继承来实现,这样双方之间处于强链接,类之间关联性极强,如要进行扩展,必然导致类结构急剧膨胀。采用桥接模式,正是为了避免这一情况的发生,将一方与桥绑定,即实现桥接口,另一方在抽象类中调用桥接口(指向的实现类),这样桥方可以通过实现桥接口进行单方面扩展,而另一方可以继承抽象类而单方面扩展,而之间的调用就从桥接口来作为突破口,不会受到双方扩展的任何影响。
三、桥接模式的角色
使用桥接模式制造不同的计算器外壳的类图:
Abstraction(抽象类):用于定义抽象类的接口,它一般是抽象类而不是接口,其中定义了一个Implementor(实现类接口)类型的对象并可以维护该对象,它与Implementor之间具有关联关系,它既可以包含抽象业务方法,也可以包含具体业务方法。
RefinedAbstraction(扩充抽象类):扩充由Abstraction定义的接口,通常情况下它不再是抽象类而是具体类,它实现了在Abstraction中声明的抽象业务方法,在RefinedAbstraction中可以
调用在Implementor中定义的业务方法。
Implementor(实现类接口):定义实现类的接口,这个接口不一定要与Abstraction的接口完全一致,事实上这两个接口可以完全不同,一般而言,Implementor接口仅提供基本操作,而Abstraction定义的接口可能会做更多更复杂的操作。Implementor接口对这些基本操作进行了声明,而具体实现交给其子类。通过关联关系,在Abstraction中不仅拥有自己的方法,还可以调用到Implementor中定义的方法,使用关联关系来替代继承关系。
ConcreteImplementor(具体实现类):具体实现Implementor接口,在不同的ConcreteImplementor中提供基本操作的不同实现,在程序运行时,ConcreteImplementor对象将替换其父类对象,提供给抽象类具体的业务操作方法。
四、使用桥接模式制造计算器外壳
4.1 抽象外壳类
public abstract class Shell {
protected Color color;
public void setColor(Color color) {
this.color = color;
}
public abstract void make();
}
4.2 抽象颜色类
public abstract class Color {
public abstract void paint();
}
4.3 具体外壳类
public class PlasticShell extends Shell {
@Override
public void make() {
System.out.println("Use plastics make shell!");
color.paint();
}
}
public class MetalShell extends Shell {
@Override
public void make() {
System.out.println("Use metals make shell!");
color.paint();
}
}
4.4 具体颜色类
public class RedColor extends Color {
@Override
public void paint() {
System.out.println("Paint Red!");
}
}
public class GreenColor extends Color {
@Override
public void paint() {
System.out.println("Paint Green!");
}
}
4.5 客户端类
public class Client {
public static void makeShell() {
Color red = new RedColor();
Color green = new GreenColor();
Shell plasticShell = new PlasticShell();
plasticShell.setColor(red);
plasticShell.make();
plasticShell.setColor(green);
plasticShell.make();
Shell metalShell= new MetalShell();
metalShell.setColor(red);
metalShell.make();
metalShell.setColor(green);
metalShell.make();
}
public static void main(String[] args) {
makeShell();
}
}
- 运行结果
Use plastics make shell!
Paint Red!
Use plastics make shell!
Paint Green!
Use metals make shell!
Paint Red!
Use metals make shell!
Paint Green!
在客户端中实现了外壳材质与颜色的自由组合。
五、桥接模式总结
5.1 优点
-
分离抽象接口及其实现部分。桥接模式使用 “对象间的关联关系” 解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。所谓抽象和实现沿着各自维度的变化,也就是说抽象和实现不再在同一个继承层次结构中,而是 “子类化” 它们,使它们各自都具有自己的子类,以便任何组合子类,从而获得多维度组合对象。
-
在很多情况下,桥接模式可以取代多层继承方案,多层继承方案违背了 “单一职责原则”,复用性较差,且类的个数非常多,桥接模式是比多层继承方案更好的解决方法,它极大减少了子类的个数。
-
桥接模式提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,符合 “开闭原则”。
5.2 缺点
-
桥接模式的使用会增加系统的理解与设计难度,由于关联关系建立在抽象层,要求开发者一开始就针对抽象层进行设计与编程。
-
桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性,如何正确识别两个独立维度也需要一定的经验积累。
5.3 适用场景:
-
如果一个系统需要在抽象化和具体化之间增加更多的灵活性,避免在两个层次之间建立静态的继承关系,通过桥接模式可以使它们在抽象层建立一个关联关系。
-
“抽象部分” 和 “实现部分” 可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
-
一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度都需要独立进行扩展。
-
对于那些不希望使用继承或因为多层继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
参考