【设计模式】桥接模式 Bridge Pattern
开篇还是引用吕振宇老师的那篇经典的文章《设计模式随笔-蜡笔与毛笔的故事》。这个真是太经典了,没有比这个例子能更好的阐明桥接模式了,这里我就直接盗来用了。
现在市面上卖的蜡笔很多,各种型号,各种颜色种类繁多, 假如一盒蜡笔有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<T, V> : IExportorExcutor<T, V>
where T : Exportor, new()
where V : Database, new()
{
public static IExportorExcutor<T, V> Of()
{
return new ExportorExcutor<T, V>();
}
public void Execute()
{
var export = new T();
var database = new V();
export.SetDatabase(database);
export.Export();
}
}
客户端调用代码:
static void Main(string[] args)
{
ExportorExcutor<ExcelExportor, SQLiteDatabase>.Of().Execute();
ExportorExcutor<ExcelExportor, OracalDatabase>.Of().Execute();
ExportorExcutor<ExcelExportor, SQLDatabase>.Of().Execute();
ExportorExcutor<ExcelExportor, DBaseDatabase>.Of().Execute(); ExportorExcutor<PDFExportor, SQLiteDatabase>.Of().Execute();
ExportorExcutor<PDFExportor, OracalDatabase>.Of().Execute();
ExportorExcutor<PDFExportor, SQLDatabase>.Of().Execute();
ExportorExcutor<PDFExportor, DBaseDatabase>.Of().Execute(); ExportorExcutor<TxtExportor, SQLiteDatabase>.Of().Execute();
ExportorExcutor<TxtExportor, OracalDatabase>.Of().Execute();
ExportorExcutor<TxtExportor, SQLDatabase>.Of().Execute();
ExportorExcutor<TxtExportor, DBaseDatabase>.Of().Execute(); ExportorExcutor<XmlExportor, SQLiteDatabase>.Of().Execute();
ExportorExcutor<XmlExportor, OracalDatabase>.Of().Execute();
ExportorExcutor<XmlExportor, SQLDatabase>.Of().Execute();
ExportorExcutor<XmlExportor, DBaseDatabase>.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]
桥接模式就探讨到这里。
【设计模式】桥接模式 Bridge Pattern的更多相关文章
- 转:设计模式-----桥接模式(Bridge Pattern)
转自:http://www.cnblogs.com/houleixx/archive/2008/02/23/1078877.html 记得看原始链接的评论. 学习设计模式也有一段时间了,今天就把我整理 ...
- C#设计模式——桥接模式(Bridge Pattern)
一.概述在软件开发中,我们有时候会遇上一个对象具有多个变化维度.比如对汽车对象来说,可能存在不同的汽车类型,如公共汽车.轿车等,也可能存在不同的发动机,如汽油发动机.柴油发动机等.对这类对象,可应用桥 ...
- 乐在其中设计模式(C#) - 桥接模式(Bridge Pattern)
原文:乐在其中设计模式(C#) - 桥接模式(Bridge Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 桥接模式(Bridge Pattern) 作者:webabcd 介绍 ...
- python 设计模式之桥接模式 Bridge Pattern
#写在前面 前面写了那么设计模式了,有没有觉得有些模式之间很类似,甚至感觉作用重叠了,模式并不是完全隔离和独立的,有的模式内部其实用到了其他模式的技术,但是又有自己的创新点,如果一味地认为每个模式都是 ...
- 二十四种设计模式:桥接模式(Bridge Pattern)
桥接模式(Bridge Pattern) 介绍将抽象部分与它的实现部分分离,使它们都可以独立地变化. 示例有一个Message实体类,对它的操作有Insert()和Get()方法,现在使这些操作的抽象 ...
- 桥接模式(Bridge Pattern)
1,定义 桥接模式(Bridge Pattern),也称为桥梁模式,其用意是将抽象化与实现化脱耦,使得两者可以独立的变化,它可以使软件系统沿着多个方向进行变化,而又不引入额外的复杂 ...
- Net设计模式实例之桥接模式( Bridge Pattern)
一.桥接模式简介(Brief Introduction) 桥接模式(Bridge Pattern),将抽象部分与它的实现部分分离,使的抽象和实现都可以独立地变化. Decouple an abstra ...
- 七个结构模式之桥接模式(Bridge Pattern)
问题: 当存在多个独立的变化维度时,如果仍采用多层继承结构,会急剧的增加类的个数,因此可以考虑将各个维度分类,使他们不相互影响. 定义: 将抽象部分与它的实现部分进行分离,抽象部分只保留最为本质的部分 ...
- 设计模式 -- 桥接模式(Bridge)
写在前面的话:读书破万卷,编码如有神--------------------------------------------------------------------主要内容包括: 初始桥接模式 ...
随机推荐
- 在ASP.NET Core中使用EPPlus导入出Excel文件
这篇文章说明了如何使用EPPlus在ASP.NET Core中导入和导出.xls/.xlsx文件(Excel).在考虑使用.NET处理excel时,我们总是寻找第三方库或组件.使用Open Offic ...
- [区块链|非对称加密] 对数字证书(CA认证)原理的回顾
摘要:文中首先解释了加密解密的一些基础知识和概念,然后通过一个加密通信过程的例子说明了加密算法的作用,以及数字证书的出现所起的作用.接着对数字证书做一个详细的解释,并讨论一下windows中数字证书的 ...
- 给女朋友讲解什么是Optional【JDK 8特性】
前言 只有光头才能变强 前两天带女朋友去图书馆了,随手就给她来了一本<与孩子一起学编程>的书,于是今天就给女朋友讲解一下什么是Optional类. 至于她能不能看懂,那肯定是看不懂的.(学 ...
- windows代码,路径分割
BOOL SplitPathName( PWSTR MyXbpathBuffer, wstring& wdrive, wstring& wdir, wstring& wfnam ...
- 学习ELk之----02. Elastic Search操作入门
我们将使用Postman来进行日志写入操作.Postman的下载地址,你可以Google一下. 1. 在上一节中,我们启动完成ELK的Docker后,可以在浏览器中打开:http://192.168. ...
- 20171201 - macOS High Sierra 神级 bug
昨日亲测有效,macOS High Sierra 神级 bug,系统管理员 root 密码为空,输入就可以登录,具备最高权限. 让人不禁想象 Apple Software 怎么了,人才都流失了吗?
- node项目自动化部署--基于Jenkins,Docker,Github(1)安装Jenkins
前言 每次项目代码更新后都要重新部署,如果只有一台服务器还好. 但是如果是分布式系统,动不动就很多台服务器,所以代码的自动部署就显得十分重要了. 这里用几篇文章来记录一下如何使用Jenkins,Doc ...
- PEACHPIE 0.9.11 版本发布,可以上生产了
PeachPie在官方博客(https://www.peachpie.io/2018/10/release-0911-visual-studio.html)发布了PeachPie的0.9.11版本 - ...
- r.js合并实践
项目中用到require.js做生产时模块开发,但上线要合并压缩,幸好它配套有r.js.下面就其用法说明一下. 首先建一个目录,里面的结构如下: require.js可以到r.js项目下载 r.js可 ...
- Java Main参数解析(Args4j)
最近实现一个工具,Main函数会有很多参数,而且参数类型不同,为了统一解析,网上找到三方工具类Args4j,轻松搞定. 代码实例如下: 定义解析类: import java.io.File impor ...