.NET设计模式-适配器模式(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/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

时间: 2024-11-08 20:54:08

.NET设计模式-适配器模式(Adapter Pattern)的相关文章

解读设计模式----适配器模式(Adapter Pattern)

在金庸笔下,三大神功都是难得之宝,多少人为得到他而......,仔细的分析下这三大神功,还是北冥较好,呵呵.我们从软件设计的角度来看,这不知算不算得上是一种复用(功力复用)的思想,只不过有点残忍罢.而在软件设计领域里,"复用"在某些时候也会出现很多问题,比如平台不兼容,开发语言不同或是接口不同等多种原因,弄得不好会不会出现既浪费了别人的现有资源,而自己的系统又无法完成呢?这有点像吸星----损人又损己. 企图将设计做好,就能够一劳永逸地坐享其成,这样的愿望就好上面所提到的吸星神功一般,

乐在其中设计模式(C#) - 适配器模式(Adapter Pattern)

原文:乐在其中设计模式(C#) - 适配器模式(Adapter Pattern)[索引页][源码下载] 乐在其中设计模式(C#) - 适配器模式(Adapter Pattern) 作者:webabcd 介绍 将一个类的接口转换成客户希望的另外一个接口.Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作. 示例 有一个Message实体类,某个类对它的操作有Insert()和Get()方法.现在需要把这个类转到另一个接口,分别对应Add()和Select()方法. Mess

Python设计模式Adapter Pattern例子详解

Python设计模式 3种类型:  1. 创建型     *灵活方式创建对象*          - 抽象工厂          - 建造者          - 工厂方法          - 原型          - 单例  2. 结构型      *将一种对象改装为另一种对象,或将小对象合成大对象*          - 适配器          - 桥接          - 组合          - 修饰器          - 外观          - 享元         

C#设计模式(7)——适配器模式(Adapter Pattern)

原文:C#设计模式(7)--适配器模式(Adapter Pattern) 一.引言 在实际的开发过程中,由于应用环境的变化(例如使用语言的变化),我们需要的实现在新的环境中没有现存对象可以满足,但是其他环境却存在这样现存的对象.那么如果将"将现存的对象"在新的环境中进行调用呢?解决这个问题的办法就是我们本文要介绍的适配器模式--使得新环境中不需要去重复实现已经存在了的实现而很好地把现有对象(指原来环境中的现有对象)加入到新环境来使用. 二.适配器模式的详细介绍 2.1 定义  下面让我

设计模式(五)适配器模式Adapter(结构型)

设计模式(五)适配器模式Adapter(结构型) 1. 概述:          接口的改变,是一个需要程序员们必须(虽然很不情愿)接受和处理的普遍问题.程序提供者们修改他们的代码;系统库被修正;各种程序语言以及相关库的发展和进化.         例子1:iphone4,你即可以使用UBS接口连接电脑来充电,假如只有iphone没有电脑,怎么办呢?苹果提供了iphone电源适配器.可以使用这个电源适配器充电.这个iphone的电源适配器就是类似我们说的适配器模式.(电源适配器就是把电源变成需要

设计模式之适配器模式(adaptor pattern)

1 适配器模式的定义 在设计模式中,适配器模式(英语:adapter pattern)有时候也称包装样式或者包装(wrapper).将一个类的接口转接成用户所期待的.一个适配使得因接口不兼容而不能在一起工作的类工作在一起,做法是将类自己的接口包裹在一个已存在的类中. 2 实际应用中的例子 比如我们去日本买了一个日本的电饭煲,但日本电压标准都是110V,而我们国家标准是220V,所以要想在中国用日本电饭煲,需要一个电源适配器. 比如我们有一个micro SD卡,如果我们想在笔记本上用,就需要一个读

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

原文 第6章 适配器模式(Adapter Pattern) 概述 将一个类的接口转换成客户希望的另外一个接口.Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作.  解决的问题 即Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作. 注:        在GoF的设计模式中,对适配器模式讲了两种类型,类适配器模式和对象适配器模式.由于类适配器模式通过多重继承对一个接口与另一个接口进行匹配,而C#.java等语言都不支持多重继承,因而这里只是介绍

【设计模式】—— 适配器模式Adapter

模式意图 如果已经有了一种类,而需要调用的接口却并不能通过这个类实现.因此,把这个现有的类,经过适配,转换成支持接口的类. 换句话说,就是把一种现有的接口编程另一种可用的接口. 模式结构 [类的适配器] Target 目标接口 Adaptee 现有的类 Adapter 中间转换的类,即实现了目标接口,又继承了现有的类. 1 package com.xingoo.test1; 2 interface Target{ 3 public void operation1(); 4 public void

Java设计模式--适配器模式

适配器模式(别名:包装类) 将一个类的接口转换成客户希望的另外一个接口.Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作. Adapter Pattern(Another Name: wrapper) Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise becau