一、为何需要适配器模式(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 转换成目标接口 SpecificRequest
imageFileAdaptee.SpecificRequest();
}
}
//Client
class 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();
}
}
//Client
class 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() { }
}
//Client
public 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();
}
}