桥接模式(Bridge Pattern)
桥接模式的定义:
桥接模式也叫做桥梁模式,其定义:将抽象和实现解耦,使得两者可以独立地变化。
桥接模式的优点:
1. 抽象和实现分离。它基本上是为了解决继承的缺点而提出来的设计模式,在该模式下,实现可以不受抽象的约束。
2. 良好的扩展能力。无论是增加抽象还是增加实现,都是非常容易实现的。
桥接模式的使用场景:
1. 不希望或不适用使用继承的场景。例如,继承层次过渡、类组合爆炸等。
2. 接口或抽象类不稳定的场景。
3. 重用性要求比较高的场景。
桥接模式的通用类图:
Abstraction(抽象化角色):定义出该角色的行为,同时保存一个对实现化的角色引用,该角色一般是抽象类。
Implementor(实现化角色):定义了角色的行为和属性,一般为接口或抽象类。
RefinedAbstraction(具体抽象化角色):它引用实现化角色对抽象化角色进行实现。
ConcreteImplementor(具体实现化角色):他实现了实现化角色定义的接口
桥接模式的通用代码:
抽象化角色:
/*抽象化角色*/
public abstract class Abstraction
{
/*抽象化角色需要持有实现化的一个引用*/
private Implementor implementor;
public Abstraction(Implementor implementor)
{
this.implementor = implementor;
}
/*基本抽象化角色功能*/
public void request()
{
this.implementor.doSomething1();
}
public Implementor getImplementor()
{
return this.implementor;
}
}
实现化角色:
/*实现化角色*/
public interface Implementor
{
/*定义角色所拥有的接口*/
public void doSomething1();
public void doSomething2();
}
具体抽象化角色:
/*具体抽象化角色*/
public class RefinedAbstraction extends Abstraction
{
public RefinedAbstraction(Implementor implementor)
{
super(implementor);
}
/*抽象变化,使用实现化角色提供的功能实现具体抽象化角色*/
@Override
public void request()
{
super.request();
super.getImplementor().doSomething2();
}
}
具体实现化角色:
public class ConcreteImplementor implements Implementor
{
@Override
public void doSomething1()
{
System.out.println("doSomething1 method invoke!");
}
@Override
public void doSomething2()
{
System.out.println("doSomething2 method invoke!");
}
}
场景类:
public class Client
{
public static void main(String[] args)
{
/*创建具体实现化角色*/
Implementor implementor = new ConcreteImplementor();
/*抽象化角色使用哪一个具体实现化角色实现*/
Abstraction abstraction = new RefinedAbstraction(implementor);
abstraction.request();
}
}
理解"桥接模式"
桥接模式的定义是非常别扭的,而且刚开始接触会感到莫名其妙。"抽象与实现的解耦",按照继承的思想来解读,抽象还能与实现分开(-_-!!,$%$$&^$@#$%,直接一顿狂骂,这是忽悠人?)。冷静下来一个字一个字的解读这段定义和在网络上搜索一些示例终于将这个别扭的解释给搞明白了。所谓的"抽象和实现"不是我们所理解的继承体系中的抽象类和其派生类,"实现"是抽象类及其派生类用来实现自己功能的对象,而不是所谓的派生类。
说了一堆还是比较绕口下面以一个例子说明:有两个画图工具类DP1和DP2,现在需要制作一个系统可以使用两个画图工具制作矩形,矩形有对角线的两个点坐标决定(如下图)。
DP1 | DP2 | |
矩形 | drawRectangleByDP1(x1,y1,x2,y2) | drawRectangleByDP2(x,x,width,height) |
分析该系统,很容易获得如下类图:
抽象出来一个Shape类,为了以后可以扩展其他形状,首先派生了一个Rectangle矩形类,Rectangle类又派生两个子类用于满足使用两种画图工具制作矩形。代码如下:
Shape:
/*形状抽象了定义了形状都有一个draw方法用于绘制图形*/
public abstract class Shape
{
abstract public void draw();
}
Rectangle:
/*定义了绘制矩形需要的共同信息,起始点坐标*/
public abstract class Rectangle extends Shape
{
protected int x;
protected int y;
public Rectangle(int x, int y)
{
this.x = x;
this.y = y;
}
}
DP1Rectangle:
/*使用DP1绘制Rectangle*/
public class DP1Rectangle extends Rectangle
{
/*起始点对角线的另一端坐标*/
private int x2;
private int y2;
public DP1Rectangle(int x1, int y1, int x2, int y2)
{
super(x1, y1);
this.x2 = x2;
this.y2 = y2;
}
@Override
public void draw()
{
this.drawRectangleByDP1(super.x, super.y, x2, y2);
}
/*使用DP1绘制矩形*/
private void drawRectangleByDP1(int x1, int y1, int x2, int y2)
{
DP1.drawRectangleByDP1(x1, y1, x2, y2);
}
}
DP2Rectangle:
/*使用DP2绘制矩形*/
public class DP2Rectangle extends Rectangle
{
/*根据提供的宽与高信息绘制矩形*/
private int width;
private int height;
public DP2Rectangle(int x1, int y1, int width, int height)
{
super(x1, y1);
this.width = width;
this.height = height;
}
@Override
public void draw()
{
this.drawRectangleByDP2(super.x, super.y, width, height);
}
/*使用DP2绘制矩形*/
private void drawRectangleByDP2(int x, int y, int width, int height)
{
DP2.drawRectangleByDP2(x, y, width, height);
}
}
DP1:
public class DP1
{
/*为了方便讲方法设置成了static的*/
public static void drawRectangleByDP1(int x1, int y1, int x2, int y2)
{
/*求矩阵的宽与高*/
int width = Math.abs(x2 - x1);
int height = Math.abs(y2 - y1);
/*用"*"符号模拟矩图案*/
for(int row =0;row < height;row++)
{
for(int col =0;col<width;col++)
{
System.out.print("*");
}
System.out.println();
}
}
}
DP2:
public class DP2
{
public static void drawRectangleByDP2(int x, int y, int width, int height)
{
for(int row =0;row < height;row++)
{
for(int col =0;col<width;col++)
{
System.out.print("*");
}
System.out.println();
}
}
}
来看场景类调用:
public class Client
{
public static void main(String[] args)
{
System.out.println("Use DP1 Draw Rectangle:");
Shape shape = new DP1Rectangle(0, 0, 5, 5);
shape.draw();
System.out.println("----------------------------------------");
System.out.println("Use DP2 Draw Rectangle:");
shape = new DP2Rectangle(0, 0, 16, 5);
shape.draw();
}
}
控制台输出结果:
Use DP1 Draw Rectangle:
*****
*****
*****
*****
*****
----------------------------------------
Use DP2 Draw Rectangle:
****************
****************
****************
****************
****************
可以看到我们设计的程序能够满足需求。如果时间和客户的需求再次停止了,我们的程序是100分的,但是那是不可能发生的 (-_-!!),需求的变化几乎是软件开发不可避免的一个问题,如果此时客户要求我们的程序可以绘制三角形。根据此需求我们的类图变化如下:
为了能让程序绘制三角形,首先从Shape派生出一个Triangle类,Triangle类又派生出使用DP1和DP2工具的子类,与Rectangle的及其相似。这样一看我们好像是完成了需求,但是却隐藏了一个较严重的问题,那就是"类爆炸",比如,需要绘制的图形又增加"圆形"、"五边形"等等,每增加一种形状就会增加两个实现类,要是有10种形状那就得有20个实现类,要是绘图工具也增加几个这个实现类简直难以管理,庞大到无语的地步,我们需要将类的增加变为线性增加才行。
桥接模式是解决这类问题的好方法,我们从头来看看这个程序,每一个形状类的实现都是依靠了绘图工具,我们使用继承的方式满足了使用不同方式绘制图形,但它有类爆炸的问题存在。这时回头看看桥接模式的定义:"将抽象与实现解耦,使两者可以独立的变化",有没有发现它的的定义就是我们寻找的答案。抽象-就是我们的形状Shape,实现-就是我们的画图工具。
上面的例子使用桥接模式:
Shape:
/*抽象化角色*/
public abstract class Shape
{
/*持有实现化角色的引用*/
private DP dp = null;
public Shape(DP dp)
{
this.dp = dp;
}
protected DP getDp()
{
return dp;
}
/*具体抽象化角色实现的功能*/
abstract public void draw(int[] parameters);
}
DP:
/*实现化角色*/
public interface DP
{
/*定义了角色应有的接口*/
public void drawRectangle(int[] parameters);
}
Rectangle:
/*具体抽象化角色*/
public class Rectangle extends Shape
{
/*设置用来实现自己功能的实现化对象*/
public Rectangle(DP dp)
{
super(dp);
}
/*使用实现化对象实现自己的功能*/
@Override
public void draw(int[] parameters)
{
super.getDp().drawRectangle(parameters);
}
}
DP1:
public class DP1 implements DP
{
@Override
public void drawRectangle(int[] parameters)
{
int x1 = parameters[0];
int y1 = parameters[1];
int x2 = parameters[2];
int y2 = parameters[3];
/*求矩阵的宽与高*/
int width = Math.abs(x1 - x2);
int height = Math.abs(y1 - y2);
/*用"*"符号模拟矩图案*/
for(int row =0;row < height;row++)
{
for(int col =0;col<width;col++)
{
System.out.print("*");
}
System.out.println();
}
}
}
DP2:
public class DP2 implements DP
{
@Override
public void drawRectangle(int[] parameters)
{
int width = parameters[0];
int height = parameters[1];
/*用"*"符号模拟矩图案*/
for(int row =0;row < height;row++)
{
for(int col =0;col<width;col++)
{
System.out.print("*");
}
System.out.println();
}
}
}
看看场景类:
public class Client
{
public static void main(String[] args)
{
System.out.println("Use DP1 Draw Rectangle:");
Shape shape1 = new Rectangle(new DP1());
shape1.draw(new int[]{0,0,5,5});
System.out.println("----------------------------------------");
System.out.println("Use DP2 Draw Rectangle:");
Shape shape2 = new Rectangle(new DP2());
shape2.draw(new int[]{16,5});
}
}
控制台输出结果:
Use DP1 Draw Rectangle:
*****
*****
*****
*****
*****
----------------------------------------
Use DP2 Draw Rectangle:
****************
****************
****************
****************
****************
使用桥接模式后依然满足了需求,现在看看使用桥接模式后的类图:
有没有发现结构清晰了许多,并且完美的解决了类爆炸的问题,你想画圆形,继承Shape就可以了,你想画三角形一样只需要继承Shape类就可以了,其他不用改变。你若想增加DP3绘图工具,也只需要继承DP就可以了,对Shape没有影响,这就是使用桥接模式能够让抽象和实现解耦,两者独立变化的情况。
总结
桥接模式中所谓的抽象和实现并不是继承概念上的抽象和实现,"实现"指的是抽象类及其派生类(Shape与Rectangle类)用于实现自己功能的对象(DP及其子类)。
桥接模式是代替使用继承解决一些问题的方案,在类继承层次过于深的情况下可以使用桥接模式,抽象或接口不稳定的情况下也建议使用桥接模式。
桥接模式不难,主要一定要理解桥接模式中所谓的抽象和实现。
终于写完了(囧),个人学习心得难免有疏漏或错误之处,若有疏漏和错误请指出,谢谢~_~。2012-01-25 01:35:15
/span