浅学设计模式之桥接模式(2)

 今天一直心绪不宁,被一些事情所困扰,希望能有个好的过程以及结果而不是自己的空想。看一下桥接模式吧,个人感觉这个模式是个比较容易理解的模式,但是在真正应用的时候还是需要一定的经验。


概述

在软件系统中,某些类型由于自身的逻辑,它具有两个或多个维度的变化,那么如何应对这种“多维度的变化”?如何利用面向对象的技术来使得该类型能够轻松的沿着多个方向进行变化,而又不引入额外的复杂度?这就要使用Bridge模式。 桥接模式将继承关系转换为关联关系,从而降低了类与类之间的耦合减少了代码编写量



模式定义:

    桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。(Bridge Pattern: Decouple an abstraction from its implementation so that the two can vary independently.)

结构:


Abstraction

定义抽象的接口
该接口包含实现具体行为、具体特征的Implementor接口

Refined Abstraction

抽象接口Abstraction的子类,依旧是一个抽象的事物名

Implementor

定义具体行为、具体特征的应用接口

ConcreteImplementor

实现Implementor接口

模式分析:

理解桥接模式,重点需要理解如何将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化。
    •抽象化:抽象化就是忽略一些信息,把不同的实体当作同样的实体对待。在面向对象中,将对象的共同性质抽取出来形成类的过程即为抽象化的过程。
    •实现化:针对抽象化给出的具体实现,就是实现化,抽象化与实现化是一对互逆的概念,实现化产生的对象比抽象化更具体,是对抽象化事物的进一步具体化的产物。
    •脱耦:脱耦就是将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联,将两个角色之间的继承关系改为关联关系。桥接模式中的所谓脱耦,就是指在一个软件系统的抽象化和实现化之间使用关联关系(组合或者聚合关系)而不是继承关系,从而使两者可以相对独立地变化,这就是桥接模式的用意。

代码实现:

这个例子用画笔和颜色搭配比较合适:

Abstraction

public abstract class Brush {
	Color mColor;
	public Brush(Color color) {
		mColor = color;
	}
	
	//默认画笔为黑色
	public Brush() {
		mColor = new BlackColor();
	}
	
	public void setColor(Color color) {
		mColor = color;
	}
	public abstract void draw();
}

Refined Abstraction

小号笔:
public class SmallBrush extends Brush {
	
	public SmallBrush(){
		super();
	}
	
	public SmallBrush(Color color) {
		super(color);
	}

	@Override
	public void draw() {
		System.out.println(" in smallBrush, begin to draw");
		mColor.draw();
	}

}

中号笔:
public class MiddleBrush extends Brush {
	
	public MiddleBrush() {
		super();
	}

	public MiddleBrush(Color color) {
		super(color);
	}
	
	@Override
	public void draw() {
		System.out.println(" in MiddleBrush, begin to draw");
		mColor.draw();
	}

}

大号笔:
public class BigBrush extends Brush {
	
	public BigBrush() {
		super();
	}

	public BigBrush(Color color) {
		super(color);
	}

	@Override
	public void draw() {
		System.out.println(" in BigBrush, begin to draw");
		mColor.draw();
	}

}

Implementor

public interface Color {
	void draw();
}

ConcreteImplementor

红色:
public class RedColor implements Color {

	@Override
	public void draw() {
		System.out.println("this is red color...");
	}

}
黑色:
public class BlackColor implements Color {

	@Override
	public void draw() {
		System.out.println("this is black color...");
	}

}
蓝色:
public class BlueColor implements Color {

	@Override
	public void draw() {
		System.out.println("this is blue color...");
	}

}
绿色:
public class GreenColor implements Color {

	@Override
	public void draw() {
		System.out.println("this is green color...");
	}

}

Client

public class Client {

	public static void main(String[] args) {
		//生成五种颜色
		Color blackColor = new BlackColor();
		Color blueColor = new BlueColor();
		Color greenColor = new GreenColor();
		Color redColor = new RedColor();
		
		//生成三种笔
		Brush smallBrush = new SmallBrush(redColor);
		Brush middleBrush = new MiddleBrush();
		//设置成蓝颜色
		middleBrush.setColor(blueColor);
		//默认黑颜色
		Brush bigBrush = new BigBrush();
		
		//开始画
		smallBrush.draw();
		middleBrush.draw();
		bigBrush.draw();
		
		//把大号笔换成绿颜色,中号换成黑颜色
		bigBrush.setColor(greenColor);
		middleBrush.setColor(blackColor);
		bigBrush.draw();
		middleBrush.draw();
	}

}

再扩展下:
如果我们需要用不同颜色不同型号的笔画出不同的图形:
图形接口:
public interface Shape {
	void create();
}

图形实现:
正方形:
public class Square implements Shape {

	@Override
	public void create() {
		System.out.println("Square");
	}

}

圆形:
public class Circle implements Shape {

	@Override
	public void create() {
		System.out.println("cricle");
	}

}

心形:
public class HeartShaped implements Shape {

	@Override
	public void create() {
		System.out.println("love heart-shaped");
	}

}

修改一下画笔类:
public abstract class Brush {
	Color mColor;
	public Brush(Color color) {
		mColor = color;
	}
	
	//默认画笔为黑色
	public Brush() {
		mColor = new BlackColor();
	}
	
	public void setColor(Color color) {
		mColor = color;
	}
	
	public void drawShape(Shape shape){
		draw();
		shape.create();
	}
	
	public abstract void draw();
}
然后客户端调用:
public class Client {

	public static void main(String[] args) {
		//生成五种颜色
		Color blackColor = new BlackColor();
		Color blueColor = new BlueColor();
		Color greenColor = new GreenColor();
		Color redColor = new RedColor();
		
		//生成三种笔
		Brush smallBrush = new SmallBrush(redColor);
		Brush middleBrush = new MiddleBrush();
		//设置成蓝颜色
		middleBrush.setColor(blueColor);
		//默认黑颜色
		Brush bigBrush = new BigBrush();
		
		//开始画
		smallBrush.draw();
		middleBrush.draw();
		bigBrush.draw();
		
		//把大号笔换成绿颜色,中号换成黑颜色
		bigBrush.setColor(greenColor);
		middleBrush.setColor(blackColor);
		bigBrush.draw();
		middleBrush.draw();
		
		//用小号笔画一个红心
		smallBrush.drawShape(new HeartShaped());
		//中号笔画一个黑色圆形
		middleBrush.drawShape(new Circle());
		//用大号笔画一个绿色正方形
		bigBrush.drawShape(new Square());
	}

}


模式优点:

分离抽象和实现部分

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

更好的扩展性

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

可动态地切换实现

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

可减少子类的个数

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


桥接模式的缺点

  1. 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
  2. 桥接模式要求正确识别出系统中两个独立变化的维度,因其使用范围具有一定的局限性

适用环境

如果你不希望在抽象部分和实现部分采用固定的绑定关系,可以采用桥接模式,来把抽象部分和实现部分分开,然后在程序运行期间来动态地设置抽象部分需要用到的具体的实现,还可以动态地切换具体的实现。

如果出现抽象部分和实现部分都能够扩展的情况,可以采用桥接模式,让抽象部分和实现部分独立地变化,从而灵活地进行单独扩展,而不是搅在一起,扩展一边就会影响到另一边。

如果希望实现部分的修改不会对客户产生影响,可以采用桥接模式。由于客户是面向抽象的接口在运行,实现部分的修改可以独立于抽象部分,并不会对客户产生影响,也可以说对客户是透明的。

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

模式扩展

适配器模式与桥接模式

桥接模式和适配器模式用于设计的不同阶段,桥接模式用于系统的初步设计,对于存在两个独立变化维度的类可以将其分为抽象化和实现化两个角色,使它们可以分别进行变化;而在初步设计完成之后,当发现系统与已有类无法协同工作时,可以采用适配器模式。但有时候在设计初期也需要考虑适配器模式,特别是那些涉及到大量第三方应用接口的情况。

桥接模式和策略模式

这两个模式有很大的相似之处。

如果把桥接模式的抽象部分简化来看,暂时不去扩展Abstraction,也就是去掉RefinedAbstraction。

这两个模式虽然相似,但也还是有区别的。最主要的是模式的目的不一样,策略模式的目的是封装一系列的算法,使得这些算法可以相互替换;而桥接模式的目的是分离抽象部分和实现部分,使得它们可以独立地变化。通过上面的结构图,可以体会到桥接模式和策略模式是如此相似。可以把策略模式的Context当做是使用接口的对象,而Strategy就是某个接口了,具体的策略实现就相当于接口的具体实现。这样看来的话,某些情况下,可以使用桥接模式来模拟实现策略模式的功能。

桥接模式和状态模式

由于从模式结构上看,状态模式和策略模式是一样的,因此这两个模式的关系也基本上类似于桥接模式和策略模式的关系。

只不过状态模式的目的是封装状态对应的行为,并在内部状态改变的时候改变对象的行为。

桥接模式和模板方法模式

这两个模式有相似之处。

虽然标准的模板方法模式是采用继承来实现的,但是模板方法也可以通过回调接口的方式来实现。如果把接口的实现独立出去,那就类似于模板方法通过接口去调用具体的实现方法了,这样的结构就和简化的桥接模式类似了。

可以使用桥接模式来模拟实现模板方法模式的功能。如果在实现Abstraction对象的时候,在其中定义方法,方法中就是某个固定的算法骨架,也就是说这个方法就相当于模板方法。在模板方法模式中,是把不能确定实现的步骤延迟到子类去实现;现在在桥接模式中,把不能确定实现的步骤委托给具体实现部分去完成,通过回调实现部分的接口,来完成算法骨架中的某些步骤。这样一来,就可以实现使用桥接模式来模拟实现模板方法模式的功能。

使用桥接模式来模拟实现模板方法模式的功能,还有一个潜在的好处,就是模板方法也可以很方便地扩展和变化。在标准的模板方法中,一个问题就是当模板发生变化的时候,所有的子类都要变化,非常不方便。而使用桥接模式来实现类似的功能,就没有这个问题。

另外,这里只是说从实现具体的业务功能上,桥接模式可以模拟实现模板方法模式能实现的功能,并不是说桥接模式和模板方法模式就变成一样的,或者是桥接模式就可以替换模板方法模式了。要注意它们本身的功能、目的、本质思想都是不一样的。

桥接模式和抽象工厂模式

这两个模式可以组合使用。

桥接模式中,抽象部分需要获取相应的实现部分的接口对象,那么谁来创建实现部分的具体实现对象呢?这就是抽象工厂模式派上用场的地方。也就是使用抽象工厂模式来创建和配置一个特定的具体的实现对象。

事实上,抽象工厂主要是用来创建一系列对象的,如果创建的对象很少,或者是很简单,还可以采用简单工厂,也能达到同样的效果,但是会比抽象工厂来得简单。

桥接模式和适配器模式

这两个模式可以组合使用。

这两个模式功能是完全不一样的,适配器模式的功能主要是用来帮助无关的类协同工作,重点在解决原本由于接口不兼容而不能一起工作的那些类,使得它们可以一起工作。而桥接模式则重点在分离抽象部分和实现部分。

所以在使用上,通常在系统设计完成以后,才会考虑使用适配器模式;而桥接模式。是在系统开始的时候就要考虑使用。

虽然功能上不一样,这两个模式还是可以组合使用的,比如,已有实现部分的接口,但是有些不太适应现在新的功能对接口的需要,完全抛弃吧,有些功能还用得上,该怎么办呢?那就使用适配器来进行适配,使得旧的接口能够适应新的功能的需要。



你可能感兴趣的:(浅学设计模式之桥接模式(2))