《设计模式》中对桥接模式的定义为:
将抽象部分与它的实现部分分离,使他们可以独立地变化。别名为:Handle/Body。
Bridge模式是构造型的设计模式之一。Bridge模式基于类的最小设计原则,通过使用封装,聚合以及继承等行为来让不同的类承担不同的责任。它的主要特点是把抽象(abstraction)与行为实现(implementation)分离开来,从而可以保持各部分的独立性以及应对它们的功能扩展。
结构如下图所示:
(1)什么是桥接
在桥接模式里面,不太好理解的就是桥接的概念,什么是桥接?为何需要桥接?如何桥接?把这些问题搞清楚了,也就基本明白桥接的含义了。
一个一个来,先看什么是桥接?所谓桥接,通俗点说就是在不同的东西之间搭一个桥,让他们能够连接起来,可以相互通讯和使用。那么在桥接模式中到底是给什么东西来搭桥呢?就是为被分离了的抽象部分和实现部分来搭桥,比如前面示例中抽象的消息和具体消息发送之间搭个桥。
但是这里要注意一个问题:在桥接模式中的桥接是单向的,也就是只能是抽象部分的对象去使用具体实现部分的对象,而不能反过来,也就是个单向桥。
(2)为何需要桥接
为了达到让抽象部分和实现部分都可以独立变化的目的,在桥接模式中,是把抽象部分和实现部分分离开来的,虽然从程序结构上是分开了,但是在抽象部分实现的时候,还是需要使用具体的实现的,这可怎么办呢?抽象部分如何才能调用到具体实现部分的功能呢?很简单,搭个桥不就可以了,搭个桥,让抽象部分通过这个桥就可以调用到实现部分的功能了,因此需要桥接。
(3)如何桥接
这个理解上也很简单,只要让抽象部分拥有实现部分的接口对象,这就桥接上了,在抽象部分就可以通过这个接口来调用具体实现部分的功能。也就是说,桥接在程序上就体现成了在抽象部分拥有实现部分的接口对象,维护桥接就是维护这个关系。
(4)独立变化
桥接模式的意图:使得抽象和实现可以独立变化,都可以分别扩充。也就是说抽象部分和实现部分是一种非常松散的关系,从某个角度来讲,抽象部分和实现部分是可以完全分开的,独立的,抽象部分不过是一个使用实现部分对外接口的程序罢了。
如果这么看桥接模式的话,就类似于策略模式了,抽象部分需要根据某个策略,来选择真实的实现,也就是说桥接模式的抽象部分相当于策略模式的上下文。更原始的就直接类似于面向接口编程,通过接口分离的两个部分而已。但是别忘了,桥接模式的抽象部分,是可以继续扩展和变化的,而策略模式只有上下文,是不存在所谓抽象部分的。
那抽象和实现为何还要组合在一起呢?原因是在抽象部分和实现部分还是存在内部联系的,抽象部分的实现通常是需要调用实现部分的功能来实现的。
(5)动态变换功能
由于桥接模式中的抽象部分和实现部分是完全分离的,因此可以在运行时动态组合具体的真实实现,从而达到动态变换功能的目的。
从另外一个角度看,抽象部分和实现部分没有固定的绑定关系了,因此同一个真实实现可以被不同的抽象对象使用,反过来,同一个抽象也可以有多个不同的实现。就像前面示例的那样,比如:站内短消息的实现功能,可以被普通消息、加急消息或是特急消息等不同的消息对象使用;反过来,某个消息具体的发送方式,可以是站内短消息,或者是Email,也可以是手机短消息等具体的发送方式。
(6)退化的桥接模式
如果Implementor仅有一个实现,那么就没有必要创建Implementor接口了,这是一种桥接模式退化的情况。这个时候Abstraction和Implementor是一对一的关系,虽然如此,也还是要保持它们的分离状态,这样的话,它们才不会相互影响,才可以分别扩展。
也就是说,就算不要Implementor接口了,也要保持Abstraction和Implementor是分离的,模式的分离机制仍然是非常有用的。
(7)桥接模式和继承
继承是扩展对象功能的一种常见手段,通常情况下,继承扩展的功能变化纬度都是一纬的,也就是变化的因素只有一类。
对于出现变化因素有两类的,也就是有两个变化纬度的情况,继承实现就会比较痛苦。比如上面的示例,就有两个变化纬度,一个是消息的类别,不同的消息类别处理不同;另外一个是消息的发送方式。
从理论上来说,如果用继承的方式来实现这种有两个变化纬度的情况,最后实际的实现类应该是两个纬度上可变数量的乘积那么多个。比如上面的示例,在消息类别的纬度上,目前的可变数量是3个,普通消息、加急消息和特急消息;在消息发送方式的纬度上,目前的可变数量也是3个,站内短消息、Email和手机短消息。这种情况下,如果要实现全的话,那么需要的实现类应该是:3 X 3 = 9个。
如果要在任何一个纬度上进行扩展,都需要实现另外一个纬度上的可变数量那么多个实现类,这也是为何会感到扩展起来很困难。而且随着程序规模的加大,会越来越难以扩展和维护。
而桥接模式就是用来解决这种有两个变化纬度的情况下,如何灵活的扩展功能的一个很好的方案。其实,桥接模式主要是把继承改成了使用对象组合,从而把两个纬度分开,让每一个纬度单独去变化,最后通过对象组合的方式,把两个纬度组合起来,每一种组合的方式就相当于原来继承中的一种实现,这样就有效的减少了实际实现的类的个数。
从理论上来说,如果用桥接模式的方式来实现这种有两个变化纬度的情况,最后实际的实现类应该是两个纬度上可变数量的和那么多个。同样是上面那个示例,使用桥接模式来实现,实现全的话,最后需要的实现类的数目应该是:3 + 3 = 6个。
这也从侧面体现了,使用对象组合的方式比继承要来得更灵活。
(8)桥接模式的调用顺序示意图
桥接模式的调用顺序如图8所示:
图8 桥接模式的调用顺序示意图
具体代码实例如下
//定义一个事物的抽象,维护对行为实现(Implementor)的引用
public abstract class Shape {
protected Drawing myDrawing;
abstract public void draw();
Shape(Drawing drawing) {
myDrawing = drawing;
}
public void drawLine() {
myDrawing.drawLine();
}
public void drawCircle() {
myDrawing.drawCircle();
}
}
public class Circle extends Shape{
Circle(Drawing drawing) {
super(drawing);
// TODO Auto-generated constructor stub
}
@Override
public void draw() {
// TODO Auto-generated method stub
myDrawing.drawCircle();
}
}
public class Rectangle extends Shape {
Rectangle(Drawing drawing) {
super(drawing);
// TODO Auto-generated constructor stub
}
@Override
public void draw() {
// TODO Auto-generated method stub
myDrawing.drawLine();
myDrawing.drawLine();
drawLine();
}
}
//实现部分的抽象
public abstract class Drawing {
public abstract void drawLine();
public abstract void drawCircle();
}
public class V1Drawing extends Drawing {
@Override
public void drawCircle() {
// TODO Auto-generated method stub
DP1.draw_a_circle();
}
@Override
public void drawLine() {
// TODO Auto-generated method stub
DP1.draw_a_line();
}
}
public class V2Drawing extends Drawing{
@Override
public void drawCircle() {
// TODO Auto-generated method stub
DP2.draw_a_circle();
}
@Override
public void drawLine() {
// TODO Auto-generated method stub
DP2.draw_a_line();
}
}
public class DP1 {
public static void draw_a_line() {
System.out.println("使用DP1的draw_a_line()画线");
}
public static void draw_a_circle() {
System.out.println("使用DP1的draw_a_circle()画圆");
}
}
public class DP2 {
public static void draw_a_line() {
System.out.println("使用DP2的draw_a_line()画线");
}
public static void draw_a_circle() {
System.out.println("使用DP2的draw_a_circle()画圆");
}
}
//测试类
public class BridgeClient {
public static void main(String args[]) {
Drawing draw1 = new V1Drawing();
Drawing draw2 = new V2Drawing();
Shape shape1 = new Rectangle(draw1);
shape1.draw();
Shape shape2 = new Circle(draw2);
shape2.draw();
Shape shape3 = new Circle(draw1);
shape3.draw();
}
}
输出结果:
使用DP1的draw_a_line()画线
使用DP1的draw_a_line()画线
使用DP1的draw_a_line()画线
使用DP2的draw_a_circle()画圆
使用DP1的draw_a_circle()画圆
Bridge模式的优点:
1 分离接口及其实现部分。
2 提高可扩充性。
3 实现细节对客户透明。