设计模式之适配器模式(九)
一、引出模式
1.生活中的例子
大家都是知道,天朝家用电器的标准电压和美国标准电压是不同的,也就是我们把在天朝买的笔记本带到美国去也无法正常使用的,想要使用就必须跟换电源适配器。将电源适配器的电压换成适应美国的电压就可以正常使用的。而这个电源适配器的功能就是本篇所要学习的主题“适配器(Adapter)”。
2.软件实例
比如现在要做一个日志记录的系统,在第一版中使用文件为载体,可以对日志文件进行新增和删除,随着业务的扩大,文件日志已无法满足需求,于是在第二版中采用了数据库来存储日志,并能对日志进行CRUD。由于客户的多变,现在告诉你,这两种功能都要。
问题出现了,就是第二版中定义的接口,对第一版并不是适用,也就是说,现在的客户端无法以同样的方式直接使用第一版。想要客户端同时使用这两种,就必须额外的做些工作。
有这么几种解决方案:1.在第二版中重新在写一个第一版的功能,这就是相当于白做了,明明已经有现成的了,还要再重写。2.修改第一版,迫使第一版和第二版达成兼容共识,但是第一版不是你开发的或者第一版依赖了很多项目,那怎么办?
二、认识模式
1.适配器模式定义
将一个已存在类的接口转化成客户希望的另一个接口。适配器模式使得原本由于接口不兼容问题而不能一起工作的那些类可以一起工作。
注意定义中的几个关键字“已存在类”,“转化”.
2.解决方案
该问题的根源就是新老系统接口不兼容,具体的功能都已实现,现在要做的就是是这两个接口匹配起来。
我们可以定义一个类来实现新系统的接口,然后在内部实现时转调老系统已经实现的功能,这样通过对象组合的方式,既复用了老系统的已有的功能,又在接口上满足新系统调用的要求。
3.模式原型
Clint:客户端,调用自己需要的领域接口Itarget。
ITarget:定义客户端需要的与相关领域特定的接口。
Adaptee:已存在的接口,通常能满足客户端的功能需求,但是接口与客户端要求的特定领域接口不一致,需要被适配。
Adapter:适配器,把Adaptee适配成为Client需要的ITarget。
4.模式原型代码示例
class Program { static void Main(string[] args) { //创建需要被适配的对象 Adaptee adaptee = new Adaptee(); //创建客户端需要调用的接口对象 ITarget traget = new Adapter(adaptee); //请求处理 traget.Request(); Console.ReadKey(); } } public interface ITarget { void Request(); } /// <summary> /// 已存在的接口,需要被适配 /// </summary> public class Adaptee { public void SpecificRequest() { Console.WriteLine("调用老系统功能"); } } /// <summary> /// 适配器 /// </summary> public class Adapter : ITarget { /// <summary> /// 持有需要被适配的接口对象 /// </summary> Adaptee adaptee = null; /// <summary> /// 构造函数,传入需要被适配的对象 /// </summary> /// <param name="adaptee"></param> public Adapter(Adaptee adaptee) { this.adaptee = adaptee; } public void Request() { //转调 adaptee.SpecificRequest(); } }
三、理解模式
1.模式功能:
适配器模式主要功能是进行转化匹配,目的是复用现有的功能,而不是实现新的接口。也就是说,客户端需要的功能是已经实现好了的,不需要适配器模式来实现,适配器模式主要负责把不兼容的接口转化成客户端期望的样子就行了。
这并不是说,在适配器里面不能实现功能。适配器里面可以实现功能,这种适配器叫做“智能适配器”,再说了,在适配的过程中,也有一些需要特定的功能,才能转换过来,比如调整参数以进行适配。
2.Adaptee和ITarget的关系
适配器模式中被适配的接口Adaptee和适配成为的接口ITarget是没有关联的,也就是说,Adaptee和ITarget中的方法既可以相同,也可以不同。
3.对像组合
适配器的实现方式其实是依靠对象组合的方式。通过给适配器对象组合被被适配的对象,让后当客户端调用ITarget时,适配器会把相应的功能委托给被适配的对象去完成。
4.适配器的常见实现
在实现适配器实现时,适配器通常是一个类,一般会让适配器类去实现ITarget接口,让后在适配器的具体实现里面调用Adaptee。也就是说适配器通常是一个ITarget类型,而不是Adaptee类型。
5.智能适配器
在开发中,适配器也可以实现一些Adaptee没有的功能,但在ITarget定义了这些功能。这种情况就需要在适配器的实现里面,加入新功能的实现。这种适配器被成为智能适配器。
6.适配多个Adaptee
适配器在适配时,可以适配多个Adaptee,也就是说实现某个新的ITarget的功能时,需要调用多个模块的功能,这样才能满足新接口的要求。
7.缺省适配
缺省适配,就是为一个接口提供缺省实现。有了它,就可以不用直接去实现接口,而是采用继承这个缺省适配对象。
8.双向适配器:
先前都是把Adaptee适配成为ITarget,其实也可以将ITarget适配成为Adaptee。也就是说这个适配器类可以作为ITarget用也可以作为Adaptee用。
使用适配器有一个潜在问题,就是被适配的对象不再兼容Adaptee的接口,应为适配器只是实现了ITarget接口。这导致并不是所有的Adaptee对象可以使用的地方都能使用适配器。
双向适配器就解决了这样的问题,双向适配器同时实现了ITarget和Adaptee的接口,使得双向适配器可以在ITarget或Adaptee被使用的地方使用,以提供对说有客户的透明性。
9.对象适配器
对象适配器的实现依赖于对象组合,就像前面示例的。
10.类适配器
类适配器的实现采用多重继承对一个接口与另一个接口进行匹配。
11.对象适配器和类适配器的权衡
类适配器使用对象继承,是静态定义方式;对象适配器使用对象组合,是动态组合方式。
12.适配器模式的优点
1) 更好的复用性
2) 更好的可扩展性
13.适配器模型的缺点
过多地使用适配器,会让系统非常零乱,不容易对整体的把握。
14.何时选用适配器模式
1) 系统需要使用现有的类,而此类的接口不符合系统的需要。
2) 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。这些源类不一定有很复杂的接口。
3) (对对象适配器而言)在设计里,需要改变多个已有子类的接口,如果使用类的适配器模式,就要针对每一个子类做一个适配器,而这不太实际。
15.小结
适配器模式的本质是:转化匹配,复用功能。
适配器通过转调已有的实现,从而能把已有的实现匹配成为需要的接口,使之能满足客户端的需要。也就是说转换匹配是手段,而复用功能才是目的。