桥模式(Bridge Pattern)
桥接模式很像适配器模式,都是使用类把一个借口转换成另一种接口。
但是,适配器的意图是是使得一个或多个类的接口与某个特定类的接口看起来很像,而桥接模式的设计目的是分离类的接口和实现,这样可以改变或是更换实现而无需修改客户端的代码。
adapter模式是用来帮助无关的类协同工作,它通常在系统设计完成后才会被使用。
bridge模式则是在系统开始的时候就被使用,它使得抽象接口和实现的部分可以独立进行改变。
桥接模式的用意是"将抽象化(Abstraction)与实现化(Implementation)
脱耦,使得二者可以独立地变化"。这句话有三个关键词,也就是抽象化、实现化和脱耦。
桥接模式的参与者包括:
抽象(Abstraction),其定义了类的接口;
精化抽象(Refined Abstraction),其扩展和实现接口;
实现者(Implementer),其定义实现类的接口;
具体实现者(ConcreteImplementor),其为实现类。
桥接模式的效果:
桥接模式的目的是在保持客户端接口不变的同时,允许修改实际上要显示或是要使用的类,这可以避免重新编译一组复杂的用户接口模块,而只要求重新编译桥接部分以及实际上最终要显示的类。
可以分别扩展实现类和桥接类,通常情况下,这两者彼此之间无需过多的交互。
更加容易向客户端程序隐藏实现的细节。
(1)什么是桥接
在桥接模式里面,不太好理解的就是桥接的概念,什么是桥接?为何需要桥接?如何桥接?把这些问题搞清楚了,也就基本明白桥接的含义了。
一个一个来,先看什么是桥接?所谓桥接,通俗点说就是在不同的东西之间搭一个桥,让他们能够连接起来,可以相互通讯和使用。那么在桥接模式中到底是给什么东西来搭桥呢?就是为被分离了的抽象部分和实现部分来搭桥,比如前面示例中抽象的消息和具体消息发送之间搭个桥。
但是这里要注意一个问题:在桥接模式中的桥接是单向的,也就是只能是抽象部分的对象去使用具体实现部分的对象,而不能反过来,也就是个单向桥。
(2)为何需要桥接
为了达到让抽象部分和实现部分都可以独立变化的目的,在桥接模式中,是把抽象部分和实现部分分离开来的,虽然从程序结构上是分开了,但是在抽象部分实现的时候,还是需要使用具体的实现的,这可怎么办呢?抽象部分如何才能调用到具体实现部分的功能呢?很简单,搭个桥不就可以了,搭个桥,让抽象部分通过这个桥就可以调用到实现部分的功能了,因此需要桥接。
(3)如何桥接
这个理解上也很简单,只要让抽象部分拥有实现部分的接口对象,这就桥接上了,在抽象部分就可以通过这个接口来调用具体实现部分的功能。也就是说,桥接在程序上就体现成了在抽象部分拥有实现部分的接口对象,维护桥接就是维护这个关系。
(4)独立变化
桥接模式的意图:使得抽象和实现可以独立变化,都可以分别扩充。也就是说抽象部分和实现部分是一种非常松散的关系,从某个角度来讲,抽象部分和实现部分是可以完全分开的,独立的,抽象部分不过是一个使用实现部分对外接口的程序罢了。
如果这么看桥接模式的话,就类似于策略模式了,抽象部分需要根据某个策略,来选择真实的实现,也就是说桥接模式的抽象部分相当于策略模式的上下文。更原始的就直接类似于面向接口编程,通过接口分离的两个部分而已。但是别忘了,桥接模式的抽象部分,是可以继续扩展和变化的,而策略模式只有上下文,是不存在所谓抽象部分的。
那抽象和实现为何还要组合在一起呢?原因是在抽象部分和实现部分还是存在内部联系的,抽象部分的实现通常是需要调用实现部分的功能来实现的。
(5)动态变换功能
由于桥接模式中的抽象部分和实现部分是完全分离的,因此可以在运行时动态组合具体的真实实现,从而达到动态变换功能的目的。
从另外一个角度看,抽象部分和实现部分没有固定的绑定关系了,因此同一个真实实现可以被不同的抽象对象使用,反过来,同一个抽象也可以有多个不同的实现。就像前面示例的那样,比如:站内短消息的实现功能,可以被普通消息、加急消息或是特急消息等不同的消息对象使用;反过来,某个消息具体的发送方式,可以是站内短消息,或者是Email,也可以是手机短消息等具体的发送方式。
(6)退化的桥接模式
如果Implementor仅有一个实现,那么就没有必要创建Implementor接口了,这是一种桥接模式退化的情况。这个时候Abstraction和Implementor是一对一的关系,虽然如此,也还是要保持它们的分离状态,这样的话,它们才不会相互影响,才可以分别扩展。
也就是说,就算不要Implementor接口了,也要保持Abstraction和Implementor是分离的,模式的分离机制仍然是非常有用的。
(7)桥接模式和继承
继承是扩展对象功能的一种常见手段,通常情况下,继承扩展的功能变化纬度都是一纬的,也就是变化的因素只有一类。
对于出现变化因素有两类的,也就是有两个变化纬度的情况,继承实现就会比较痛苦。比如上面的示例,就有两个变化纬度,一个是消息的类别,不同的消息类别处理不同;另外一个是消息的发送方式。
从理论上来说,如果用继承的方式来实现这种有两个变化纬度的情况,最后实际的实现类应该是两个纬度上可变数量的乘积那么多个。比如上面的示例,在消息类别的纬度上,目前的可变数量是3个,普通消息、加急消息和特急消息;在消息发送方式的纬度上,目前的可变数量也是3个,站内短消息、Email和手机短消息。这种情况下,如果要实现全的话,那么需要的实现类应该是:3 X 3 = 9个。
如果要在任何一个纬度上进行扩展,都需要实现另外一个纬度上的可变数量那么多个实现类,这也是为何会感到扩展起来很困难。而且随着程序规模的加大,会越来越难以扩展和维护。
而桥接模式就是用来解决这种有两个变化纬度的情况下,如何灵活的扩展功能的一个很好的方案。其实,桥接模式主要是把继承改成了使用对象组合,从而把两个纬度分开,让每一个纬度单独去变化,最后通过对象组合的方式,把两个纬度组合起来,每一种组合的方式就相当于原来继承中的一种实现,这样就有效的减少了实际实现的类的个数。
从理论上来说,如果用桥接模式的方式来实现这种有两个变化纬度的情况,最后实际的实现类应该是两个纬度上可变数量的和那么多个。同样是上面那个示例,使用桥接模式来实现,实现全的话,最后需要的实现类的数目应该是:3 + 3 = 6个。
这也从侧面体现了,使用对象组合的方式比继承要来得更灵活。
简单类图:
简单桥模式的实现:
Abstraction类和RefinedAbstraction类:
public abstract class Abstraction { protected Implementor _implementor; //其实这只是一种保护措施。。。 //是维护着抽象类和实现类的一种联系。。。抽象部分和实现部分还是存在内部联系 // 抽象部分的实现通常是需要调用实现部分的功能来实现 public Implementor MyImplementor { get {return _implementor;} set { _implementor = value; } } public virtual void Operation() //抽象类的实现方式,也是很抽象的,其实就是对外提供的一种借口。 { //我们看到它具体的实现也是调用到实现类的具体实现的方法的。 _implementor.OperationImp(); } } //具体实现类RefinedAbstraction public class RefinedAbstraction : Abstraction { public override void Operation() { _implementor.OperationImp(); //具体实现类,被提炼的抽象。 } }
Implementor类和具体的两个实现类ConcreteImplementorA,ConcreteImplementorB:
public abstract class Implementor { public abstract void OperationImp(); } //具体的实现。 public class ConcreteImplementorA:Implementor { public override void OperationImp() { Console.WriteLine("{0} 操作方法!", this.GetType().Name); } } public class ConcreteImplementorB : Implementor { public override void OperationImp() { Console.WriteLine("{0} 操作方法!",this.GetType().Name); } }
客户端代码:
static void Main(string[] args) { //这里的主函数,类似的客户端的调用就很简单了。。。 Abstraction a1 = new RefinedAbstraction(); //可以被申明为子类的类型。具体实现类,被提炼的抽象。 //就像是我用客户端的方式去调用的时抽象类。 //对具体实现的一种调用。。。 //首先是对A实现的调用。。。 a1.MyImplementor = new ConcreteImplementorA(); //申明具体的实现方式。 a1.Operation(); //然后是对B具体实现的调用。。。 a1.MyImplementor = new ConcreteImplementorB(); a1.Operation(); // Console.ReadKey(); }运行截图:
最后上个类之间的依赖图,可以直观的看清两个抽象类之间的桥是怎么实现的:就是一个两个抽象类之间的引用。