开篇还是引用吕振宇老师的那篇经典的文章《设计模式随笔-蜡笔与毛笔的故事》。这个真是太经典了,没有比这个例子能更好的阐明桥接模式了,这里我就直接盗来用了。
现在市面上卖的蜡笔很多,各种型号,各种颜色种类繁多, 假如一盒蜡笔有24种颜色,那么它能涂抹出24种不同的颜色来,蜡笔型号是固定的,如果想画出各种线条那么就要购买不同型号的蜡笔,假如我们要涂抹出粗,中,细三种线条,那么我们就要买3盒粗,中,细型号的蜡笔才能满足需求,那么就是3盒*24色=72只蜡笔。假如使用毛笔来作画,我们需要准备3只粗,中,细的毛笔和24种颜料就好了, 那么就是3只毛笔+24种颜料。使用毛笔和使用蜡笔的差别是:用毛笔只需要3+24=27,用蜡笔需要准备3*24=72. 为什么会出现这么大的差别呢?仔细分析就会发现,画画的时候不仅对笔的型号有要求,而且还对颜色有要求,也就是说有两个引起变化的点或者说有两个变化的维度。蜡笔的型号和颜色直接绑定在一起了,他们二者完全融合(耦合)在一起了,他们的属性从生产出来就已经固化了(是静态的),不能被改变了。 而毛笔的型号和颜料的颜色是毫无关系(解耦),毛笔厂家生产不同型号的毛笔,颜料厂家生产不同颜色的颜料,二者互不相干,只有在使用的时候用户决定用什么型号的毛笔蘸什么颜色的颜料(动态设置)来作画,这个时候毛笔和颜料才动态发生关系,如果用户想使用一个型号的毛笔画不同颜色的画,毛笔可以洗掉再蘸不同的颜色就可以。
在看看蜡笔和毛笔在应对变化的优劣比较, 如果用户需要画一个加粗线条,蜡笔需要买一盒(24),毛笔只需要买一支就可以了。如果要加一种颜色,蜡笔需要增加4支(加粗,粗,中,细),而毛笔仅仅只需要增加一种颜色就够了。 从数学的角度来讲蜡笔不管是颜色或者型号的变化都会形成:型号数*颜色数,毛笔却是:型号数+颜色数。这样看来毛笔更有优势,更容易应对变化的需求。
那么在软件开发的过程中也会碰到类似的问题,怎么来解决这类问题呢?这就是我们将要探讨的桥接模式(Brigde)。
一、桥接模式的定义
桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。
二、桥接模式结构图
1、Abstraction(抽象类):
用于定义抽象类的接口,它一般是抽象类而不是接口,其中定义了一个Implementor(实现类接口)类型的对象并可以维护该对象,它与Implementor之间具有关联关系,它既可以包含抽象业务方法,也可以包含具体业务方法。
2、RefinedAbstraction(扩充抽象类):
扩充由Abstraction定义的接口,通常情况下它不再是抽象类而是具体类,它实现了在Abstraction中声明的抽象业务方法,在RefinedAbstraction中可以调用在Implementor中定义的业务方法。
3、Implementor(实现类接口):
定义实现类的接口,这个接口不一定要与Abstraction的接口完全一致,事实上这两个接口可以完全不同,一般而言,Implementor接口仅提供基本操作,而Abstraction定义的接口可能会做更多更复杂的操作。Implementor接口对这些基本操作进行了声明,而具体实现交给其子类。通过关联关系,在Abstraction中不仅拥有自己的方法,还可以调用到Implementor中定义的方法,使用关联关系来替代继承关系。
4、ConcreteImplementor(具体实现类):
具体实现Implementor接口,在不同的ConcreteImplementor中提供基本操作的不同实现,在程序运行时,ConcreteImplementor对象将替换其父类对象,提供给抽象类具体的业务操作方法。
三、桥接模式经典实现
public abstract class Implementor { public abstract void Operation(); } public abstract class Abstraction { protected Implementor implementor; public Implementor Implementor { set { this.implementor = value; } } public virtual void Operation() { implementor.Operation(); } } public class ConcreteImplementorA : Implementor { public override void Operation() { Console.WriteLine(this.GetType().Name + " Operation"); } } public class ConcreteImplementorB : Implementor { public override void Operation() { Console.WriteLine(this.GetType().Name + " Operation"); } } public class RefinedAbstraction : Abstraction { public override void Operation() { base.Operation(); } }
客户端调用:
static void Main(string[] args) { Abstraction abstraction = new RefinedAbstraction(); Implementor implementor = new ConcreteImplementorA(); abstraction.Implementor = implementor; abstraction.Operation(); implementor = new ConcreteImplementorB(); abstraction.Implementor = implementor; abstraction.Operation(); Console.ReadKey(); }
输出结果:
四、桥接模式的实例
我们分别模拟实现开头提到的蜡笔和毛笔。我们只选择三种型号(Large,Middle,Small)和三种颜色(Red,Green, Blue).
1、蜡笔的实现
蜡笔类代码:
public abstract class Crayon { protected string size; protected string color; protected abstract void SetSize(); protected abstract void SetColor(); public void Display() { SetSize(); SetColor(); Console.WriteLine(this.GetType().Name + ": [Size]=" + size + "[Color]=" + color); } } public abstract class LargeCrayon : Crayon { protected override void SetSize() { size = "Large"; } } public abstract class MiddleCrayon : Crayon { protected override void SetSize() { size = "Middle"; } } public abstract class SmallCrayon : Crayon { protected override void SetSize() { size = "Small"; } } public class RedLargeCrayon : LargeCrayon { protected override void SetColor() { color = "Red"; } } public class GreenLargeCrayon : LargeCrayon { protected override void SetColor() { color = "Green"; } } public class BlueLargeCrayon : LargeCrayon { protected override void SetColor() { color = "Blue"; } } public class RedMiddleCrayon : MiddleCrayon { protected override void SetColor() { color = "Red"; } } public class GreenMiddleCrayon : MiddleCrayon { protected override void SetColor() { color = "Green"; } } public class BlueMiddleCrayon : MiddleCrayon { protected override void SetColor() { color = "Blue"; } } public class RedSmallCrayon : SmallCrayon { protected override void SetColor() { color = "Red"; } } public class GreenSmallCrayon : SmallCrayon { protected override void SetColor() { color = "Green"; } } public class BlueSmallCrayon : SmallCrayon { protected override void SetColor() { color = "Blue"; } }
客户端调用:
static void Main(string[] args) { Crayon.Crayon redLargeCrayon, greenLargeCrayon, blueLargeCrayon, redMiddleCrayon, greenMiddleCrayon, blueMiddleCrayon, redSmallCrayon, greenSmallCrayon, blueSmallCrayon; redLargeCrayon = new RedLargeCrayon(); greenLargeCrayon = new GreenLargeCrayon(); blueLargeCrayon = new BlueLargeCrayon(); redMiddleCrayon = new RedMiddleCrayon(); greenMiddleCrayon = new GreenMiddleCrayon(); blueMiddleCrayon = new BlueMiddleCrayon(); redSmallCrayon = new RedSmallCrayon(); greenSmallCrayon = new GreenSmallCrayon(); blueSmallCrayon = new BlueSmallCrayon(); redLargeCrayon.Display(); greenLargeCrayon.Display(); blueLargeCrayon.Display(); redMiddleCrayon.Display(); greenMiddleCrayon.Display(); blueMiddleCrayon.Display(); redSmallCrayon.Display(); greenSmallCrayon.Display(); blueSmallCrayon.Display(); Console.ReadKey(); }
输出:
蜡笔是一种典型的多层继承结构, 型号和颜色是在继承体系中得以实现,在程序编译的时候型号和颜色就已经绑定好了,在运行时无法再动态改变,并且类非常多,如果增加型号或者颜色将非常难以维护。
2、毛笔的实现
毛笔类结构图
毛笔类代码:
public abstract class Brush { private Color color; protected string size; public void SetColor(Color color) { this.color = color; } protected abstract void SetSize(); public void Draw() { SetSize(); Console.WriteLine(this.GetType().Name + ": [Size]=" + this.size + "->[Color]=" + this.color.CurrentColor); } } public abstract class Color { protected string color; public string CurrentColor { get { return color; } } } public class RedColor:Color { public RedColor() { this.color = "Red"; } } public class GreenColor:Color { public GreenColor() { this.color = "Green"; } } public class BlueColor : Color { public BlueColor() { this.color = "Blue"; } } public class LargeBrush : Brush { protected override void SetSize() { this.size = "Large"; } } public class MiddleBrush : Brush { protected override void SetSize() { this.size = "Middle"; } } public class SmallBrush : Brush { protected override void SetSize() { this.size = "Small"; } }
客户端调用:
static void Main(string[] args) { Brush largeBrush, middleBrush, smallBrush; Color red, green, blue; red = new RedColor(); green = new GreenColor(); blue = new BlueColor(); largeBrush = new LargeBrush(); middleBrush = new MiddleBrush(); smallBrush = new SmallBrush(); largeBrush.SetColor(red); largeBrush.Draw(); largeBrush.SetColor(green); largeBrush.Draw(); largeBrush.SetColor(blue); largeBrush.Draw(); middleBrush.SetColor(red); middleBrush.Draw(); middleBrush.SetColor(green); middleBrush.Draw(); middleBrush.SetColor(blue); middleBrush.Draw(); smallBrush.SetColor(red); smallBrush.Draw(); smallBrush.SetColor(green); smallBrush.Draw(); smallBrush.SetColor(blue); smallBrush.Draw(); Console.ReadKey(); }
输出结果:
LargeBrush: [Size]=Large->[Color]=Red LargeBrush: [Size]=Large->[Color]=Green LargeBrush: [Size]=Large->[Color]=Blue MiddleBrush: [Size]=Middle->[Color]=Red MiddleBrush: [Size]=Middle->[Color]=Green MiddleBrush: [Size]=Middle->[Color]=Blue SmallBrush: [Size]=Small->[Color]=Red SmallBrush: [Size]=Small->[Color]=Green SmallBrush: [Size]=Small->[Color]=Blue
毛笔类之间的结构发生了一些变化,将蜡笔的深度继承关系变成了一个平行的关联关系,这样带来的好处是毛笔的型号和颜色可以在两个体系中独立的变化而互不影响。这样就降低了耦合度,提高了扩展性,和可维护性,使得类的数量也急剧减少,降低了复杂度。
五、桥接模式的优点
- 分离抽象接口及其实现部分。桥接模式使用“对象间的关联关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。所谓抽象和实现沿着各自维度的变化,也就是说抽象和实现不再在同一个继承层次结构中,而是“子类化”它们,使它们各自都具有自己的子类,以便各自子类的组合,从而获得多维度组合对象。
- 在很多情况下,桥接模式可以取代多层继承方案,多层继承方案违背了“单一职责原则(SRP)”,复用性较差,且类的个数非常多,桥接模式是比多层继承方案更好的解决方法,它极大减少了类的个数。
- 桥接模式提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,符合“开闭原则(OCP)”。
六、桥接模式的缺点
- 桥接模式的使用会增加系统的理解与设计难度,由于关联关系建立在抽象层,要求开发者一开始就就要针对抽象层进行设计与编程。
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性,如何正确识别两个独立维度也需要一定的经验积累。
七、桥接模式的使用场景
- 如果一个系统需要在抽象化和具体化之间增加更多的灵活性,避免在两个层次之间建立静态的继承关系,通过桥接模式可以使它们在抽象层建立一个关联关系。
- “抽象部分”和“实现部分”可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
- 一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度都需要独立进行扩展。
- 对于那些不希望使用继承或因为多层继承导致系统类的个数急剧增加的系统,使用桥接模式。
八、练习
某软件公司欲开发一个数据转换工具,可以将数据库中的数据转换成多种文件格式,例如txt、xml、pdf等格式,同时该工具需要支持多种不同的数据库。试使用桥接模式对其进行设计。
可以使用桥接模式做一个简单的实现如下:
public abstract class Database { public abstract string GetData(); } public abstract class Exportor { private Database database; public void SetDatabase(Database database) { this.database = database; } public void Export() { var data = this.database.GetData(); var fileType = this.GetFileType(); Console.WriteLine(this.GetType().Name + "[Database] is [" + data + "] [FileType] is [" + fileType + "]"); } protected abstract string GetFileType(); } public class SQLDatabase : Database { public override string GetData() { return "SQLDatabase"; } } public class OracalDatabase : Database { public override string GetData() { return "OracalDatabase"; } } public class SQLiteDatabase : Database { public override string GetData() { return "SQLiteDatabase"; } } public class DBaseDatabase : Database { public override string GetData() { return "DBaseDatabase"; } } public class ExcelExportor : Exportor { protected override string GetFileType() { return "Excel"; } } public class TxtExportor : Exportor { protected override string GetFileType() { return "TxT"; } } public class XmlExportor : Exportor { protected override string GetFileType() { return "XML"; } } public class PDFExportor : Exportor { protected override string GetFileType() { return "PDF"; } }
客户端调用:
static void ExecuteExport() { Exportor exportExcel, exportPdf, exportXml, exportTxt; Database sql, sqlite, dbase, oracal; sql = new SQLDatabase(); sqlite = new SQLiteDatabase(); dbase = new DBaseDatabase(); oracal = new OracalDatabase(); exportExcel = new ExcelExportor(); exportPdf = new PDFExportor(); exportTxt = new PDFExportor(); exportXml = new XmlExportor(); exportXml.SetDatabase(sql); exportXml.Export(); exportXml.SetDatabase(oracal); exportXml.Export(); }
输出:
XmlExportor[Database] is [SQLDatabase] [FileType] is [XML] XmlExportor[Database] is [OracalDatabase] [FileType] is [XML]
抽出一个泛型执行器,使客户端调用代码更优雅一点,泛型执行器的代码如下:
public interface IExportorExcutor<in T, in V> where T : Exportor where V : Database { void Execute(); } public class ExportorExcutor: IExportorExcutor where T : Exportor, new() where V : Database, new() { public static IExportorExcutor Of() { return new ExportorExcutor (); } public void Execute() { var export = new T(); var database = new V(); export.SetDatabase(database); export.Export(); } }
客户端调用代码:
static void Main(string[] args) { ExportorExcutor.Of().Execute(); ExportorExcutor .Of().Execute(); ExportorExcutor .Of().Execute(); ExportorExcutor .Of().Execute(); ExportorExcutor .Of().Execute(); ExportorExcutor .Of().Execute(); ExportorExcutor .Of().Execute(); ExportorExcutor .Of().Execute(); ExportorExcutor .Of().Execute(); ExportorExcutor .Of().Execute(); ExportorExcutor .Of().Execute(); ExportorExcutor .Of().Execute(); ExportorExcutor .Of().Execute(); ExportorExcutor .Of().Execute(); ExportorExcutor .Of().Execute(); ExportorExcutor .Of().Execute(); Console.ReadKey(); }
输出:
ExcelExportor[Database] is [SQLiteDatabase] [FileType] is [Excel] ExcelExportor[Database] is [OracalDatabase] [FileType] is [Excel] ExcelExportor[Database] is [SQLDatabase] [FileType] is [Excel] ExcelExportor[Database] is [DBaseDatabase] [FileType] is [Excel] PDFExportor[Database] is [SQLiteDatabase] [FileType] is [PDF] PDFExportor[Database] is [OracalDatabase] [FileType] is [PDF] PDFExportor[Database] is [SQLDatabase] [FileType] is [PDF] PDFExportor[Database] is [DBaseDatabase] [FileType] is [PDF] TxtExportor[Database] is [SQLiteDatabase] [FileType] is [TxT] TxtExportor[Database] is [OracalDatabase] [FileType] is [TxT] TxtExportor[Database] is [SQLDatabase] [FileType] is [TxT] TxtExportor[Database] is [DBaseDatabase] [FileType] is [TxT] XmlExportor[Database] is [SQLiteDatabase] [FileType] is [XML] XmlExportor[Database] is [OracalDatabase] [FileType] is [XML] XmlExportor[Database] is [SQLDatabase] [FileType] is [XML] XmlExportor[Database] is [DBaseDatabase] [FileType] is [XML]
桥接模式就探讨到这里。