设计模式之Bridge(桥接模式)

一、问题的提出

如果有如下需求,需要画三种图形:五角星、正方形和圆,而却这三种图像分别可以着三种颜色:红色、蓝色和红色。于是就产生了红色的星星、黄色的星星、绿色的正方形、黄色的正方形等等。现在是三种图形,三种颜色,于是我们就一共去声明了九个类,当然我们还不算中间过程中的辅助父类。如果我们现在是18种图形,12种颜色,那么我们就需要写18*12个类,然后加上12个颜色的类和一个总父类,一共我们需要12*18+12+1=229个类。每增加一种图像,就会在如下图的“图形“中增加一个分支,每增加一种颜色就需要在每种图像的分支下添加一种颜色,那么我们的类就会爆炸性的增长。该怎么解决这个问题?这就要用到桥接模式。

二、桥接模式的定义

Bridge模式是一种抽象与其实现相分离的模式。它主要应用于:当事物是一组变化量,和对这些事物的操作方法(实现)也是一组变化量的情况,也就是说它们都是多变的。某些类型由于自身的逻辑,它具有两个或多个维度的变化,桥接模式就是应对这种“多维度的变化,?利用面向对象的技术来使得该类型能够轻松的沿着多个方向进行变化,而又不引入额外的复杂度。桥接模式沟通着“功能的类层次”和“实现的类层次”。将“功能的类层次”和“实现的类层次”分成2个独立的类层次可能会弄得支离破碎,所以必须在2个类层次之间建一座沟通的桥梁。比如上面提到的画一个有颜色的图形正方形,颜色和正方形是我们最终要的图像的两个维度,一个是主体一个是次体(我找不到更好的语言来表达)。

三、桥接模式的类结构图

Abstraction:
        抽象部分的接口。通常在这个对象里面,要维护一个实现部分的对象引用,在抽象对象里面的方法,需要调用实现部分的对象来完成。这个对象里面的方法,通常都是跟具体的业务相关的方法。
RefinedAbstraction:
        扩展抽象部分的接口,通常在这些对象里面,定义跟实际业务相关的方法,这些方法的实现通常会使用Abstraction中定义的方法,也可能需要调用实现部分的对象来完成。
Implementor:
        定义实现部分的接口,这个接口不用和Abstraction里面的方法一致,通常是由Implementor接口提供基本的操作,而Abstraction里面定义的是基于这些基本操作的业务方法,也就是说Abstraction定义了基于这些基本操作的较高层次的操作。
ConcreteImplementor:
        真正实现Implementor接口的对象。

运用桥接模式来解决画带颜色的图形这个问题,我们的模型图大概可以画成这样:


这种处理模式,我们只有3个表示图像的类,3个表示颜色的类,再加上一个将颜色和图像组合好的图形类,一共才7个类。如果我们需要增加图形或者颜色,都是分别独立的增加,互相不影响。

四、具体的实例代码

只做一个参考例子,所以图形只定义两种(正方形和圆),颜色也只定义两种(red和green)

/**
 * 画图形的接口
 *
 */
public interface ShapeImplementor {

	void draw();
}

/**
 * 画一个正方形
 *
 */
public class Square implements ShapeImplementor {

	@Override
	public void draw() {
		System.out.println("Draw a Square.");
	}

}

/**
 * 画一个三角形
 *
 */
public class Triangle implements ShapeImplementor {

	@Override
	public void draw() {
		System.out.println("Draw a Triangle.");
	}

}

/**
 * 给所画图形着色的基类。
 */
public abstract class ColorAbstract {
    //持有一个实现部分的对象
	protected ShapeImplementor  shape ;
	public ColorAbstract(ShapeImplementor shape){
		this.shape = shape;
	}
	/**
	 * 画一个具体的图形
	 */
	public void draw(){
		shape.draw();
	}
}

/**'
 * 颜色green的具体实现
 *
 */
public class GreenColor extends ColorAbstract {

	public GreenColor(ShapeImplementor shape) {
		super(shape);
	}

	public void draw(){
		super.draw();
		System.out.println("the color is green!");
	}
}

/**
 * 颜色red的具体实现
 */
public class RedColor extends ColorAbstract {

	public RedColor(ShapeImplementor shape) {
		super(shape);
	}
   
	public void draw(){
		super.draw();
		//为所画的图形涂上颜色
		System.out.println("the color is red!");
	}
}

/**
 * 
 *模拟的客户端
 */
public class Client {

	public static void main(String[] args) {
		ShapeImplementor  triange = new Triangle();
		ColorAbstract red = new RedColor(triange);
		red.draw();
		ColorAbstract green = new GreenColor(triange);
		green.draw();
		
	}
}

运行结果如下:

Draw a Triangle.
the color is red!
Draw a Triangle.
the color is green!

五、桥接模式与策略模式的区别

桥接模式和策略模式很相似,首先我们贴出两者的类结构图:

                                                桥接模式的类结构图


                                          策略模式的类结构图


从他们的结构图可知,在这两种模式中,都存在一个对象使用聚合的方式引用另一个对象的抽象接口的情况,而且该抽象接口的实现可以有多种并且可以替换。可以说两者在表象上都是调用者与被调用者之间的解耦,以及抽象接口与实现的分离。

那么两者的区别体现在什么地方呢?

1. 首先,在形式上,两者还是有一定区别的,对比两幅结构图,我们可以发现,在桥接模式中不仅Implementor具有变化 (ConcreateImplementior),而且Abstraction也可以发生变化(RefinedAbstraction),而且两者的变化是完全独立的,RefinedAbstraction与ConcreateImplementior之间松散耦合,它们仅仅通过Abstraction与 Implementor之间的关系联系起来。而在策略模式中,并不考虑Context的变化,只有算法的可替代性。

2. 其次在语意上,桥接模式强调Implementor接口仅提供基本操作,而Abstraction则基于这些基本操作定义更高层次的操作。而策略模式强调Strategy抽象接口的提供的是一种算法,一般是无状态、无数据的,而Context则简单调用这些算法完成其操作。

3. 桥接模式 中不仅定义Implementor的接口而且定义Abstraction的接口,Abstraction的接口不仅仅是为了与Implementor通信 而存在的,这也反映了结构型模式的特点:通过继承、聚合的方式组合类和对象以形成更大的结构。在策略模式中,Startegy和Context的接口都是 两者之间的协作接口,并不涉及到其它的功能接口,所以它是行为模式的一种。行为模式的主要特点就是处理的是对象之间的通信方式,往往是通过引入中介者对象将通信双方解耦,在这里实际上就是将Context与实际的算法提供者解耦。

所以相对策略模式,桥接模式要表达的内容要更多,结构也更加 复杂。桥接模式表达的主要意义其实是接口隔离的原则,即把本质上并不内聚的两种体系区别开来,使得它们可以松散的组合,而策略在解耦上还仅仅是某一个算法的层次,没有到体系这一层次。从结构图中可以看到,策略的结构是包容在桥接结构中的,桥接中必然存在着策略模式,Abstraction与 Implementor之间就可以认为是策略模式,但是桥接模式一般Implementor将提供一系列的成体系的操作,而且Implementor是具 有状态和数据的静态结构。而且桥接模式Abstraction也可以独立变化。

六、桥接模式的优点

1、分离抽象和实现部分
    桥接模式分离了抽象和实现部分,从而极大地提高了系统的灵活性。让抽象部分和实现部分独立开来,分别定义接口,这有助于对系统进行分层,从而产生更好的结构化的系统。对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了。

2、更好的扩展性
    由于桥接模式把抽象和实现部分分离开了,而且分别定义接口,这就使得抽象部分和实现部分可以分别独立的扩展,而不会相互影响,从而大大的提高了系统的可扩展性。

3、可动态切换实现
    由于桥接模式把抽象和实现部分分离开了,那么在实现桥接的时候,就可以实现动态的选择和使用具体的实现,也就是说一个实现不再是固定的绑定在一个抽象接口上了,可以实现运行期间动态的切换实现。

4、可减少子类的个数
    根据前面的讲述,对于有两个变化纬度的情况,如果采用继承的实现方式,大约需要两个纬度上的可变化数量的乘积个子类;而采用桥接模式来实现的话,大约需要两个纬度上的可变化数量的和个子类。可以明显地减少子类的个数

七、何时使用桥接模式

1、  如果采用继承的实现方案,会导致产生很多子类,对于这种情况,可以考虑采用桥接模式,分析功能变化的原因,看看是否能分离成不同的纬度,然后通过桥接模式来分离它们,从而减少子类的数目。

2、 如果需要用两种维度来刻画一个对象,并且这两种维度都是可以任意增减变化的,那么可以用桥接模式,分清楚哪种维度是主体,哪种维度是次体,主体就用ConcreteImplementor去实现,次体就用RefinedAbstraction去实现。

【参考文献】1、研磨设计模式:http://chjavach.iteye.com/blog/756233

 2、网络资料:http://www.blogjava.net/wangle/archive/2007/04/25/113545.html





你可能感兴趣的:(设计模式,java)