`
yangyou230
  • 浏览: 1644801 次
文章分类
社区版块
存档分类

第7章 适配器模式(Adapter Pattern)

 
阅读更多

适配器模式(Adapter Pattern)

——.NET设计模式系列之八

Terrylee,2006年2月

概述

在软件系统中,由于应用环境的变化,常常需要将“一些现存的对象”放在新的环境中应用,但是新环境要求的接口是这些现存对象所不满足的。那么如何应对这种“迁移的变化”?如何既能利用现有对象的良好实现,同时又能满足新的应用环境所要求的接口?这就是本文要说的Adapter 模式。

意图

将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

结构图

图1 类的Adapter模式结构图

图2 对象的Adapter模式结构图

生活中的例子

适配器模式允许将一个类的接口转换成客户期望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以一起工作。扳手提供了一个适配器的例子。一个孔套在棘齿上,棘齿的每个边的尺寸是相同的。在美国典型的边长为1/2''和1/4''。显然,如果不使用一个适配器的话,1/2''的棘齿不能适合1/4''的孔。一个1/2''至1/4''的适配器具有一个1/2''的阴槽来套上一个1/2''的齿,同时有一个1/4的阳槽来卡入1/4''的扳手。

图3使用扳手适配器例子的适配器对象图

适配器模式解说

我们还是以日志记录程序为例子说明Adapter模式。现在有这样一个场景:假设我们在软件开发中要使用一个第三方的日志记录工具,该日志记录工具支持数据库日志记录DatabaseLog和文本文件记录FileLog两种方式,它提供给我们的API接口是Write()方法,使用方法如下:

Log.Write("Logging Message!");

当软件系统开发进行到一半时,处于某种原因不能继续使用该日志记录工具了,需要采用另外一个日志记录工具,它同样也支持数据库日志记录DatabaseLog和文本文件记录FileLog两种方式,只不过它提供给我们的API接口是WriteLog()方法,使用方法如下:

Log.WriteLog("Logging Message!");

该日志记录工具的类结构图如下:

图4日志记录工具类结构图

它的实现代码如下:

public abstract class LogAdaptee

{

public abstract void WriteLog();

}

public class DatabaseLog:LogAdaptee

{

public override void WriteLog()

{

Console.WriteLine("Called WriteLog Method");

}

}

public class FileLog:LogAdaptee

{

public override void WriteLog()

{

Console.WriteLine("Called WriteLog Method");

}

}

在我们开发完成的应用程序中日志记录接口中(不妨称之为ILogTarget接口,在本例中为了更加清楚地说明,在命名上采用了Adapter模式中的相关角色名字),却用到了大量的Write()方法,程序已经全部通过了测试,我们不能去修改该接口。代码如下:

public interface ILogTarget

{

void Write();

}

这时也许我们会想到修改现在的日志记录工具的API接口,但是由于版权等原因我们不能够修改它的源代码,此时Adapter模式便可以派上用场了。下面我们通过Adapter模式来使得该日志记录工具能够符合我们当前的需求。

前面说过,Adapter模式有两种实现形式的实现结构,首先来看一下类适配器如何实现。现在唯一可行的办法就是在程序中引入新的类型,让它去继承LogAdaptee类,同时又实现已有的ILogTarget接口。由于LogAdaptee有两种类型的方式,自然我们要引入两个分别为DatabaseLogAdapter和FileLogAdapter的类。

图5 引入类适配器后的结构图

实现代码如下:

public class DatabaseLogAdapter:DatabaseLog,ILogTarget

{

public void Write()

{

WriteLog();

}

}

public class FileLogAdapter:FileLog,ILogTarget

{

public void Write()

{

this.WriteLog();

}

}

这里需要注意的一点是我们为每一种日志记录方式都编写了它的适配类,那为什么不能为抽象类LogAdaptee来编写一个适配类呢?因为DatabaseLog和FileLog虽然同时继承于抽象类LogAdaptee,但是它们具体的WriteLog()方法的实现是不同的。只有继承于该具体类,才能保留其原有的行为。

我们看一下这时客户端的程序的调用方法:

public class App

{

public static void Main()

{

ILogTarget dbLog = new DatabaseLogAdapter();

dbLog.Write("Logging Database...");

ILogTarget fileLog = new FileLogAdapter();

fileLog.Write("Logging File...");

}

}

下面看一下如何通过对象适配器的方式来达到我们适配的目的。对象适配器是采用对象组合而不是使用继承,类结构图如下:

图6引入对象适配器后的结构图

实现代码如下:

public class LogAdapter:ILogTarget

{

private LogAdaptee _adaptee;

public LogAdapter(LogAdaptee adaptee)

{

this._adaptee = adaptee;

}

public void Write()

{

_adaptee.WriteLog();

}

}

与类适配器相比较,可以看到最大的区别是适配器类的数量减少了,不再需要为每一种具体的日志记录方式来创建一个适配器类。同时可以看到,引入对象适配器后,适配器类不再依赖于具体的DatabaseLog类和FileLog类,更好的实现了松耦合。

再看一下客户端程序的调用方法:

public class App

{

public static void Main()

{

ILogTarget dbLog = new LogAdapter(new DatabaseLog());

dbLog.Write("Logging Database...");

ILogTarget fileLog = new LogAdapter(new FileLog());

fileLog.Write("Logging Database...");

}

}

通过Adapter模式,我们很好的实现了对现有组件的复用。对比以上两种适配方式,可以总结出,在类适配方式中,我们得到的适配器类DatabaseLogAdapter和FileLogAdapter具有它所继承的父类的所有的行为,同时也具有接口ILogTarget的所有行为,这样其实是违背了面向对象设计原则中的类的单一职责原则,而对象适配器则更符合面向对象的精神,所以在实际应用中不太推荐类适配这种方式。再换个角度来看类适配方式,假设我们要适配出来的类在记录日志时同时写入文件和数据库,那么用对象适配器我们会这样去写:

public class LogAdapter:ILogTarget

{

private LogAdaptee _adaptee1;

private LogAdaptee _adaptee2;

public LogAdapter(LogAdaptee adaptee1,LogAdaptee adaptee2)

{

this._adaptee1 = adaptee1;

this._adaptee2 = adaptee2;

}

public void Write()

{

_adaptee1.WriteLog();

_adaptee2.WriteLog();

}

}

如果改用类适配器,难道这样去写:

public class DatabaseLogAdapter:DatabaseLog,FileLog,ILogTarget

{

public void Write()

{

//WriteLog();

}

}

显然是不对的,这样的解释虽说有些牵强,也足以说明一些问题,当然了并不是说类适配器在任何情况下都不使用,针对开发场景不同,某些时候还是可以用类适配器的方式。

.NET中的适配器模式

1.Adapter模式在.NET Framework中的一个最大的应用就是COM Interop。COM Interop就好像是COM和.NET之间的一条纽带,一座桥梁。我们知道,COM组件对象与.NET类对象是完全不同的,但为了使COM客户程序象调用COM组件一样调用.NET对象,使.NET程序

象使用.NET对象一样使用COM组件,微软在处理方式上采用了Adapter模式,对COM对象进行包装,这个包装类就是RCW(Runtime Callable Wrapper)。RCW实际上是runtime生成的一个.NET类,它包装了COM组件的方法,并内部实现对COM组件的调用。如下图所示:

图7 .NET程序与COM互相调用示意图

2..NET中的另一个Adapter模式的应用就是DataAdapter。ADO.NET为统一的数据访问提供了多个接口和基类,其中最重要的接口之一是IdataAdapter。与之相对应的DataAdpter是一个抽象类,它是ADO.NET与具体数据库操作之间的数据适配器的基类。DataAdpter起到了数据库到DataSet桥接器的作用,使应用程序的数据操作统一到DataSet上,而与具体的数据库类型无关。甚至可以针对特殊的数据源编制自己的DataAdpter,从而使我们的应用程序与这些特殊的数据源相兼容。注意这是一个适配器的变体。

实现要点

1.Adapter模式主要应用于“希望复用一些现存的类,但是接口又与复用环境要求不一致的情况”,在遗留代码复用、类库迁移等方面非常有用。

2.Adapter模式有对象适配器和类适配器两种形式的实现结构,但是类适配器采用“多继承”的实现方式,带来了不良的高耦合,所以一般不推荐使用。对象适配器采用“对象组合”的方式,更符合松耦合精神。

3.Adapter模式的实现可以非常的灵活,不必拘泥于GOF23中定义的两种结构。例如,完全可以将Adapter模式中的“现存对象”作为新的接口方法参数,来达到适配的目的。

4.Adapter模式本身要求我们尽可能地使用“面向接口的编程”风格,这样才能在后期很方便的适配。[以上几点引用自MSDN WebCast]

效果

对于类适配器:

1.用一个具体的Adapter类对Adaptee和Taget进行匹配。结果是当我们想要匹配一个类以及所有它的子类时,类Adapter将不能胜任工作。

2.使得Adapter可以重定义Adaptee的部分行为,因为Adapter是Adaptee的一个子类。

3.仅仅引入了一个对象,并不需要额外的指针一间接得到Adaptee.

对于对象适配器:

1.允许一个Adapter与多个Adaptee,即Adaptee本身以及它的所有子类(如果有子类的话)同时工作。Adapter也可以一次给所有的Adaptee添加功能。

2.使得重定义Adaptee的行为比较困难。这就需要生成Adaptee的子类并且使得Adapter引用这个子类而不是引用Adaptee本身。

适用性

在以下各种情况下使用适配器模式:

1.系统需要使用现有的类,而此类的接口不符合系统的需要。

2.想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。这些源类不一定有很复杂的接口。

3.(对对象适配器而言)在设计里,需要改变多个已有子类的接口,如果使用类的适配器模式,就要针对每一个子类做一个适配器,而这不太实际。

总结

总之,通过运用Adapter模式,就可以充分享受进行类库迁移、类库重用所带来的乐趣。

参考资料

阎宏,《Java与模式》,电子工业出版社

James W. Cooper,《C#设计模式》,电子工业出版社

Alan Shalloway James R. Trott,《Design Patterns Explained》,中国电力出版社

MSDN WebCast 《C#面向对象设计模式纵横谈(7):Adapter 适配器模式(结构型模式)》

作者:TerryLee
出处:http://terrylee.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
分享到:
评论

相关推荐

    24个设计模式与6大设计原则

    第 8 章 适配器模式【ADAPTER PATTERN】 51 第 9 章 模板方法模式【TEMPLATE METHOD PATTERN】 63 第 10 章 建造者模式【BUILDER PATTERN】 82 第 11 章 桥梁模式【BRIDGE PATTERN】 97 第 12 章...

    网络编程、常用设计模式

    详细的jsp网络编程、常用设计模式! 前言 3 第一部分:网络编程 4 ...一、适配器模式(Adapter Pattern) 25 二、类的Adapter模式的结构 25 三、对象的Adapter模式的结构 26 四、在什么情况下使用适配器模式 26

    设计模式 design pattern

    第3章 创建型模式 54 3.1 Abstract Factory(抽象工厂)— 对象创建型模式 57 3.2 Builder(生成器)—对象创建型 模式 63 3.3 Factory Method(工厂方法)— 对象创建型模式 70 3.4 Prototype(原型)—对象创建型 ...

    Java设计模式

    第 8 章 适配器模式【ADAPTER PATTERN】 .............................................................................................. 51 第 9 章 模板方法模式【TEMPLATE METHOD PATTERN】 ...................

    DesignPattern

    --第七章:抽象工厂模式(Abstract Factory) 本质:选择产品簇的实现 --第八章:生成器模式(Builder) 本质:分离整体构建算法和部件构造 --第九章:原型模式(Prototype) 本质:克隆生成对象 --第十章:中介者...

    OOPractice:《HeadFirst 设计模式》中主要模式的具体实现

    OOPractice 设计模式项目练习 目录 ...第七章 模板方法 template 第八章 迭代器与组合模式 iterator 第九章 状态模式 statepattern 第十章 代理模式 proxypattern 参考书籍 《Head First 设计模式》

    《设计模式》中文版(23个设计模式的介绍与运用)

    第3章 创建型模式 54 3.1 Abstract Factory(抽象工厂)— 对象创建型模式 57 3.2 Builder(生成器)—对象创建型 模式 63 3.3 Factory Method(工厂方法)— 对象创建型模式 70 3.4 Prototype(原型)—对象创建型 ...

    java8源码-LambdaPractice:《java8实战》代码练习

    1、添加设计模式代码实践,命令模式command,适配器模式adapter ####第八周完成工作 1、第12章 新的日期和时间API实战 2、添加设计模式代码实践,装饰模式decorator,工厂模式factory,单例模式singletonInstance ####...

    设计模式 GOF 23

    第3章 创建型模式 54 3.1 Abstract Factory(抽象工厂)— 对象创建型模式 57 3.2 Builder(生成器)—对象创建型 模式 63 3.3 Factory Method(工厂方法)— 对象创建型模式 70 3.4 Prototype(原型)—对象创建型 ...

    设计模式:可复用面向对象软件的基础--详细书签版

    第3章 创建型模式 54 3.1 abstract factory(抽象工厂)— 对象创建型模式 57 3.2 builder(生成器)—对象创建型 模式 63 3.3 factory method(工厂方法)— 对象创建型模式 70 3.4 prototype(原型)—对象...

    asp.net知识库

    深入剖析ASP.NET组件设计]一书第三章关于ASP.NET运行原理讲述的补白 asp.net 运行机制初探(httpModule加载) 利用反射来查看对象中的私有变量 关于反射中创建类型实例的两种方法 ASP.Net应用程序的多进程模型 NET委托...

    二十三种设计模式【PDF版】

    设计模式之 Adapter(适配器) 使用类再生的两个方式:组合(new)和继承(extends),这个已经在 thinking in java中提到过. 设计模式之 Proxy(代理) 以 Jive 为例,剖析代理模式在用户级别授权机制上的应用 设计模式之 ...

    java 面试题 总结

    7、说出ArrayList,Vector, LinkedList的存储性能和特性 ArrayList和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组...

    超级有影响力霸气的Java面试题大全文档

    当客户机第一次调用一个Stateful Session Bean 时,容器必须立即在服务器中创建一个新的Bean实例,并关联到客户机上,以后此客户机调用Stateful Session Bean 的方法时容器会把调用分派到与此客户机相关联的Bean实例...

Global site tag (gtag.js) - Google Analytics