一、为何需要适配器模式(Adapter)?

在软件设计中,某个模块里有很多公用的功能接口,其中有些公用接口需要用到不同的类当中时,会出现接口不兼容的问题。因为这些不同的类对这个相同任务的接口,都有各自代码逻辑的要求。于是每当客户对这个接口提出新需求,就会再创建一个类,再把所有方法实现一遍,就显得代码很臃肿。为了解决这个问题,只改写需要适配的某个功能,就用到了适配器模式。

比如在文件操作的模块中,都有获取文件属性、读写文件操作等共同的功能接口。在原需求里,已存在保存 txt 文件的功能。当客户有新需求时,需要扩展保存图片文件的功能。这就出现了一个问题:保存 txt 文件和保存图片虽然都是保存操作的任务,但是实现的代码逻辑并不相同。

 //保存txt文件public void SaveFile(){StreamWriter sw = new StreamWriter("test.txt");sw.Write("HelloWorld!");sw.Dispose();}//保存图片文件public void SaveFile(){//data 里随便写的数据,不要太认真byte[] data = { 11,22,33,44,55};MemoryStream ms = new MemoryStream(data);Image img = Image.FromStream(ms);img.Save("test.jpeg", ImageFormat.Jpeg);ms.Dispose();}//保存任务相同:SaveFile。但是实现的代码逻辑不同

特点:
将一个类的接口转换成客户希望的另外一个接口,以解决接口不兼容的问题。(这里的接口不是指 C# 中的 interface,而是一个功能接口)

结构:
目标(Target):定义 Client 需要使用的功能接口。
被适配(Adaptee):定义一些在 Target 已经存在且需要适配的功能接口。
适配器(Adapter):主要负责将 Adaptee 功能接口转换为跟 Target 匹配的功能接口。
客户(Client):使用做了兼容处理的功能接口。

适合应用场景特点:

  • 这个接口具有相同的任务。(比如所有类型的文件共有读写保存的功能接口。)
  • 需要适配相同任务但工作方式不同的接口。(比如不同类型文件保存的方式不同。)

主要两种适配器模式:

  • (1)对象适配器模式
    (处理指定某些功能接口不兼容的问题;Target 派生给 Adapter,Adapter 包装一个需要 Adaptee 的类实例。注重对象组合关系)
  • (2)类适配器模式
    (处理指定某些功能接口不兼容的问题;Target 派生给 Adapter, Adaptee 也派生给 Adapter,特点为多重继承。注重类之间的继承关系)

注:==在工作中推荐使用对象适配器模式。==因为如果使用类适配器,类继承关系就会使得代码结构紧耦合,修改某个基类时就会影响子类。而对象适配不仅满足用户期待的需求,还能降低代码之间的耦合性。

对象适配模式:

类适配模式:

二、例子

需求:

在迭代版本中,客户希望在保存文本文件功能的基础上,还想要增加对一个图片文件进行保存的功能。并且在将来,可能还会扩展对其他类型文件进行保存的功能。

1、对象适配器模式:

//目标类:接口、抽象类、具体类都可作为目标public class FileOperateTarget{//保存文本文件public virtual void Request(){Console.WriteLine("Save a string content as the txt,xml,ini...");}//其他所有要实现的方法//(比如获取文件属性的方法,设置默认打开方式的方法等,都是任何类型文件共有的方法)...//如果存在代码逻辑不同的方法,则可由适配器模式进行处理。//(比如在此例子中,保存文件的方法)}//被适配类:保存图片文件public class ImageFileAdaptee{public virtual void SpecificRequest(){Console.WriteLine("Save a Image content as the Img File.");}}//适配器类:主要负责将 Adaptee 类对象进行组合来适配共同方法。public class ImageFileAdapter : FileOperateTarget{//包装一个被适配类的对象(Adaptee 对象)private ImageFileAdaptee imageFileAdaptee = new ImageFileAdaptee();public override void Request(){//适配功能的转换工作,把源接口 Request 转换成目标接口 SpecificRequestimageFileAdaptee.SpecificRequest();}}//Clientclass Program{static void Main(string[] args){FileOperateTarget target = new ImageFileAdapter();target.Request();Console.ReadLine();}}

2、类适配器模式:

//目标接口:由于在C# 中类只支持单继承,而接口可支持多重继承。所以目标只能是接口 public interface IFileOperateTarget{void Request();}//被适配类:保存图片文件public class ImageFileAdaptee{public virtual void SpecificRequest(){Console.WriteLine("Save a Image content as the Img File.");}}//适配器类:主要继承 Adaptee 类 和 Target 类,实现目标接口的转换。public class ImageFileAdapter : ImageFileAdaptee, IFileOperateTarget{public void Request(){//适配功能的转换工作this.SpecificRequest();}}//Clientclass Program{static void Main(string[] args){IFileOperateTarget target = new ImageFileAdapter();target.Request();Console.ReadLine();}}

三、缺省适配器模式

  • 特殊的适配器模式,不是主要的模式;一般缺少 Adaptee 结构。
  • 专门处理指定某些功能接口不兼容的问题,而其他所有功能接口都出于未处理状态,即空方法。

缺省适配器模式:

//目标接口public interface IFileOperateTarget{void Request();//其他抽象方法...void ReadFile();void GetFileInfo();} //适配器类:实现接口中抽象方法为空方法public class DefaultAdapter : IFileOperateTarget{public virtual void Request() { }public virtual void ReadFile() { }public virtual void GetFileInfo() { }}//Clientpublic class ClientImage : DefaultAdapter{//只使用这个功能public override void Request(){Console.WriteLine("Save a Image content as the Img File.");}}//Client:以后有新需求时,可扩展此功能的代码逻辑public class ClientWord : DefaultAdapter{public override void Request(){Console.WriteLine("Save a Word content as the Word File.");}}class Program{static void Main(string[] args){ClientImage clientImage = new ClientImage();clientImage.Request();ClientWord clientWord = new ClientWord();clientWord.Request();Console.ReadLine();}}