桥接模式
意图:将抽象部分与实现部分分离,使它们都可以独立的变化。
结构图:
概述
在软件系统中,某些类型由于自身的逻辑,它具有两个或多个维度的变化,那么如何应对这种“多维度的变化”?如何利用面向对象的技术来使得该类型能够轻松的沿着多个方向进行变化,而又不引入额外的复杂度?这就要使用Bridge模式。
- 参与者
Abstraction
1. 定义抽象接口。
2. 拥有一个Implementor类型对象引用。
- RefinedAbstraction
1. 扩展Abstraction中的接口定义。
- Implementor
1. Implementor是具体实现的接口,Implementor和Abstraction接口并不一定完全一致(注:Proxy和ISubject接口一一对应),实际上这两个接口可以完全不一样,Implementor提供具体操作方法,而Abstraction提供更高层次的调用。
- ConcreteImplementor
1. 实现Implementor接口,给出具体实现。
假设我们现在有个需求:编写一个程序,使用两个绘图程序DrawProgram1和DrawProgram2之一绘制图形(矩形,圆形等),而且我们被告知,实例化图形的时候,它会知道应该使用绘图程序DrawProgram1还是DrawProgram2
根据需求,
图形类:
我们定义一个抽象的图形类Shape,在定义抽象的矩形类Retangle和和圆形类Circle,分别继承Shape。
画图程序类:
DrawProgram1和DrawProgram2,画图程序中分别有画矩形,圆形等方法。
需求在实例化图形的时候必须指定具体的画图类,假设我们现在有Retangle1,Retangle2,Circle1,Circle2.实例1调用画图程序1,实例2调用画图程序2
类图如下所示:
如果这时候,要求增加一个画图程序DrawProgram3,那么就要依次扩展Retangle,Circle的子类,增加Retangle3,Circle3来调用画图程序DrawProgram3。从这里我们可以知道,随着画图程序的增加,就要去扩展图形类型的子类,显然这不是我们想看到的。因为在这里,画图程序和图形程序是紧耦合,所以导致了“类爆炸”。所以我们在这里需要将图形类抽象上的变化和画图程序实现变化进行解耦。
由于前面是按照不同图形的继承来分类,如果我们改用按照画图程序的实现来分类呢?
现在我们继续使用四个类表示现有的图形的组合,但这里我们按照不同绘图程序派生不同图形,所以我们消除了图形类(Retangle1,Retangle2, Circle1和Circle2)和绘图程序的之间的紧耦合,从而消除了它们之间的冗余。现在把耦合转移到更高的继承层次,但问题有出现了当有新的绘图程序加入时,我们的确可以轻松地进行扩展,只要增加ShapeDP3类继承抽象类Shape就OK了,但是要实现一套一模一样的Retangle3和Circle3了。
所以冗余与耦合的问题依然存在。
这里面变化的因素有画图程序和图形类,这是一个二维的角度。
所以我们依次对其进行抽象,再将二者的抽象进行链接。
从结构图可以看出,这里面存在两种情形
一、DrawProgramming类保存Shape对象引用
二、Shape类保存DrawProgramming对象引用
首先考虑第一种情形,如果在DrawProgramming保存Shape对象引用,我们通过调用Draw()方法绘制图形,但它们必需对Shape类中的图形有所了解(Draw()方法将具体图形封装了),当我们使用OperationalDP1中的DrawCircle()方法时,就要知道Shape中的Circle类型,这违反了对象应该只对自己负责。
第二种情形,如果Shape对象使用DrawProgramming对象绘制图形时,图形无需知道具体绘图程序。当使用Circle中的Draw()方法我们只需要调用DrawProgramming中的方法DrawCircle(),因此可以让Shape保存DrawProgramming的对象引用。
分析到这里我们可以确定采用情形二实现起来相对简单,接着在抽象类Shape增加DrawProgramming对象引用,从而在Shape和DrawProgramming之间建立了一种聚集关系(has – a关系)
现在在想想桥接模式的定义:将抽象部分与它的实现部分分离,使它们都可以独立地变化。Shape类及其实现就是抽象部分,而DrawingProgramming及其实现是具体部分,通过聚集使得它们分离开来,从而应对变化和扩展更加灵活。
这里的DP1,DP2是对画图程序的扩展
接下来看下实现的代码
图形类Shape

1 /// <summary> 2 /// 桥接模式参与者Abstraction 3 /// </summary> 4 public abstract class Shape 5 { 6 private DrawingProgramming _dp; 7 8 public Shape() 9 { 10 } 11 12 public Shape(DrawingProgramming dp) 13 { 14 this.Dp = dp; 15 } 16 /// <summary> 17 /// 画图 18 /// </summary> 19 public abstract void Draw(); 20 21 public DrawingProgramming Dp 22 { 23 get { return _dp; } 24 set { _dp = value; } 25 } 26 } 27 /// <summary> 28 /// 三角形 29 /// </summary> 30 public class Triangle : Shape 31 { 32 private int _rows = 0; 33 34 public Triangle(DrawingProgramming dp, int rows) 35 : base(dp) 36 { 37 this.Rows = rows; 38 } 39 40 public override void Draw() 41 { 42 this.DrawTriangle(this.Rows); 43 } 44 45 public void DrawTriangle(int rows) 46 { 47 this.Dp.DrawTriangle(rows); 48 } 49 50 public int Rows 51 { 52 get { return _rows; } 53 set { _rows = value; } 54 } 55 } 56 /// <summary> 57 /// 长方形 58 /// </summary> 59 public class Retangle : Shape 60 { 61 private int _width = 0; 62 private int _height = 0; 63 64 public Retangle(DrawingProgramming dp, int width, int height) 65 : base(dp) 66 { 67 this.Width = width; 68 this.Height = height; 69 } 70 71 public override void Draw() 72 { 73 this.DrawRetangle(Width, Height); 74 } 75 76 public void DrawRetangle(int width, int height) 77 { 78 this.Dp.DrawRetangle(width, height); 79 } 80 81 public int Width 82 { 83 get { return _width; } 84 set { _width = value; } 85 } 86 87 public int Height 88 { 89 get { return _height; } 90 set { _height = value; } 91 } 92 }
画图程序DrawingProgramming

1 /// <summary> 2 /// 画图程序 3 /// </summary> 4 public abstract class DrawingProgramming 5 { 6 public abstract void DrawRetangle(int width, int height); 7 8 public abstract void DrawTriangle(int Rows); 9 10 } 11 12 public class OperationalDP1 : DrawingProgramming 13 { 14 public OperationalDP1() 15 { 16 } 17 18 public DP1 DP1 19 { 20 get 21 { 22 throw new System.NotImplementedException(); 23 } 24 set 25 { 26 } 27 } 28 29 public override void DrawRetangle(int width, int height) 30 { 31 DP1.DrawRetangle(width, height); 32 } 33 34 public override void DrawTriangle(int Rows) 35 { 36 DP1.DrawTriangle(Rows); 37 } 38 } 39 public class OperationalDP2 : DrawingProgramming 40 { 41 public OperationalDP2() 42 { 43 } 44 45 public DP2 DP2 46 { 47 get 48 { 49 throw new System.NotImplementedException(); 50 } 51 set 52 { 53 } 54 } 55 56 public override void DrawRetangle(int width, int height) 57 { 58 DP2.DrawRetangle(width, height); 59 } 60 61 public override void DrawTriangle(int Rows) 62 { 63 DP2.DrawTriangle(Rows); 64 } 65 } 66 67 public class DP1 68 { 69 /// <summary> 70 /// Concrete draw method. 71 /// </summary> 72 /// <param name="width"></param> 73 /// <param name="height"></param> 74 public static void DrawRetangle(int width, int height) 75 { 76 for (int i = 0; i < height; i++) 77 { 78 for (int j = 0; j < width; j++) 79 { 80 Console.Write("■"); 81 } 82 Console.WriteLine(); 83 } 84 } 85 86 public static void DrawTriangle(int Rows) 87 { 88 for (int i = 0; i < Rows; i++) 89 { 90 for (int j = 0; j < i + 1; j++) 91 { 92 Console.Write("■"); 93 } 94 Console.WriteLine(); 95 } 96 } 97 } 98 99 /// <summary> 100 /// existed drawing programming. 101 /// </summary> 102 public class DP2 103 { 104 /// <summary> 105 /// Concrete draw method. 106 /// </summary> 107 /// <param name="width"></param> 108 /// <param name="height"></param> 109 public static void DrawRetangle(int width, int height) 110 { 111 for (int i = 0; i < height; i++) 112 { 113 for (int j = 0; j < width; j++) 114 { 115 Console.Write("★"); 116 } 117 Console.WriteLine(); 118 } 119 } 120 121 public static void DrawTriangle(int Rows) 122 { 123 for (int i = 0; i < Rows; i++) 124 { 125 for (int j = 0; j < i + 1; j++) 126 { 127 Console.Write("★"); 128 } 129 Console.WriteLine(); 130 } 131 } 132 }
客户端调用Client

1 Shape shape = new Triangle(new OperationalDP1(), 3); 2 shape.Draw(); 3 shape = new Triangle(new OperationalDP2(), 3); 4 shape.Draw(); 5 shape = new Retangle(new OperationalDP1(), 2, 3); 6 shape.Draw(); 7 shape = new Retangle(new OperationalDP2(), 2, 3); 8 shape.Draw(); 9 Console.Read();
输出结果
通过上面的例子我们对桥接模式(Bridge)有了初步的了解,假设我们的绘图程序DP1拥有特有擦除方法Wipe(),从而需要在OperationalDP1类中增加相应的方法,但没有必要修改DrawingProgramming类,现在问题出现了我们要在抽象类Shpe中增加Wipe()方法,但这种修改是我们愿意看到的。这时我们可以考虑以下两种方法解决问题。
方法一:在基类中添加虚的方法,然后需要的子类重写基类的虚方法。
方法二:使用.NET Framework中的扩展方法,对基类方法进行扩展。
桥接模式(Bridge)优点:
将实现予以解耦,让它和界面之间不再永久绑定。
抽象和实现可以独立扩展,不会影响到对方。
对于具体实现的修改,不会影响到客户端。
桥接模式(Bridge)缺点:
增加了设计复杂度。
抽象类的修改影响到子类。
桥接模式(Bridge)用途:
适用在需要跨多平台的图形和窗口系统。
当需要用不同的方式改变接口和实现时。
通过上述的介绍,我们了解为什么需要桥接模式(Bridge)和如何使用桥接模式(Bridge),由于对象的多维度的变化,使得难以决定变化时,我们可以把对象和变化抽象出来。
如果我们的对象依赖于抽象,对于具体的实现并不关心,我们可以通过对象组合,组合出我们想要的对象。桥接模式符合OCP(对于扩展开发,对于修改关闭)设计模式的原则