[Head First设计模式]身边的设计模式——适配器模式

时间:2023-03-10 00:07:45
[Head First设计模式]身边的设计模式——适配器模式

系列文章

[Head First设计模式]山西面馆中的设计模式——装饰者模式

[Head First设计模式]山西面馆中的设计模式——观察者模式

[Head First设计模式]山西面馆中的设计模式——建造者模式

[Head First设计模式]饺子馆(冬至)中的设计模式——工厂模式

[Head First设计模式]一个人的平安夜——单例模式

[Head First设计模式]抢票中的设计模式——代理模式

[Head First设计模式]面向对象的3特征5原则

[Head First设计模式]鸭子模型——策略模式

[Head First设计模式]云南米线馆中的设计模式——模版方法模式

[Head First设计模式]餐馆中的设计模式——命令模式

适配器

假设我们已经有一个软件系统,原来使用了一个第三方类库A。现在有一个新的第三方类库B,其功能等各方面都更加强大。我们希望用B来替换A,以改善我们的系统。但是B的接口与A不一样。那怎么办呢?

方案一:重建一个新系统,实现B接口
[Head First设计模式]身边的设计模式——适配器模式
方案二:找个中间的适配器
[Head First设计模式]身边的设计模式——适配器模式

适配器模式定义

将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。

  • 有时,为复用而设计的工具箱类不能够被复用的原因仅仅是因为它的接口与专业应用领域所需要的接口不匹配(名称不一样,参数不一样等等)
  • 我们可以改变工具箱类使它兼容专业领域中的类的接口,但前提是必须有这个工具箱的源代码,然而 即使我们得到了这些源代码,修改工具箱也是没有什么意义的,因为不应该仅仅为了实现一个应用,工具箱就不得不采用一些与特定领域相关的接口。
  • 我们可以不用上面的方法,而定义一个适配器类,由他来适配工具箱接口和专业应用的接口。我们可以用两种方法做这件事:
  1. 继承专业应用类的接口和工具箱类的实现。这种方法对应Adapter模式的类版本(多继承)
  2. 将工具箱类的实现作为适配器类的组成部分,并且使用工具箱的接口实现适配器类。这种方法对应Adapter模式的对象版本。

适用性:

以下情况使用Adapter模式:

  • 你想使用一个已经存在的类,而它的接口不符合你的需求。
  • 你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。

类图

适配器模式分为类适配器模式和对象适配器模式。

类适配器模式类图:

[Head First设计模式]身边的设计模式——适配器模式

基于类的Adapter模式的一般结构:Adaptee类为Adapter的父类,Adaptee类为适配源,适配目标(接口)也是Adapter的父类,基于类的Adapter模式比较适合应用于Adapter想修改Adaptee的部分方法的情况。

对象适配器模式:

[Head First设计模式]身边的设计模式——适配器模式

基于对象的Adapter模式的一般结构:Adaptee类对象为Adapter所依赖,适配目标(接口)是Adapter的父类。

基于对象的Adapter模式比较适合应用于Adapter想为Adaptee添加新的方法的情况。但在Adaptee类的方法与Adapter类的方法不同名而实现功能的情况下,我们一般也使用基于对象的Adapter模式。

参与者

Target:client使用的与特定领域相关的“接口”。

Client:与符合Target接口的对象协同的专业系统。

Adaptee:一个已经存在的“接口”,它具有Client要求的功能但不符合Client的接口要求。这个接口需要适配。

Adapter:对Adaptee的接口与Target接口进行适配。

协作

Client在Adapter实例上调用一些操作(请求)。接着适配器调用Adaptee的操作实现这个请求。

 效果(类适配器和对象适配器有不同的权衡)

类适配器

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

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

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

对象适配器

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

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

代码

简单模拟一下,中国人说汉语,美国人说英语。

 namespace Wolfy.适配器模式
{
/// <summary>
/// 汉语语言接口
/// </summary>
public interface IChinese
{
void Speak();
}
}

IChinese

 namespace Wolfy.适配器模式
{
/// <summary>
/// 英语语言接口
/// </summary>
public interface IEnglish
{
void Speak();
}
}

IEnglish

 namespace Wolfy.适配器模式
{
public class Chinese:IChinese
{
public void Speak()
{
Console.WriteLine("你好");
}
}
}

Chinese

 namespace Wolfy.适配器模式
{
public class American:IEnglish
{
public void Speak()
{
Console.WriteLine("Hello");
}
}
}

American

我们原来有个程序使用的中国人的对象,现在想让它使用美国人对象,但是现在美国人和中国人的语言接口不同,不能直接使用。
写一个美国人的适配器,让他看起来像中国人。

对象适配器模式:

 namespace Wolfy.适配器模式
{
/// <summary>
/// 让美国人适配器类 实现汉语接口
/// </summary>
public class AmericanAdapter:IChinese
{
/// <summary>
/// 美国人适配器包装了一个美国人对象,同时实现了汉语接口。这样就可以像使用中国人对象一样使用美国人对象了。
/// </summary>
IEnglish American;
public AmericanAdapter(IEnglish American)
{
this.American = American;
}
public void Speak()
{
American.Speak();
}
}
}

测试:

  class Program
{
static void Main(string[] args)
{
Chinese me = new Chinese();
American American = new American();
//美国人适配器 让他看起来像中国人对象 这里IChinese
IChinese adapter = new AmericanAdapter(American);
Console.WriteLine("美国人说:");
American.Speak();
Console.WriteLine("中国人说:");
Test(me);
Console.WriteLine("美国人适配器说:");
//在需要中国人对象的地方使用了美国人配器对象,
//美国人适配器对象包装了一个实现英语接口的美国人对象,所以实际使用的是美国人对象。
Test(adapter);
Console.Read();
}
/// <summary>
/// 测试
/// </summary>
/// <param name="chinese">实现汉语接口的中国人对象</param>
static void Test(IChinese chinese)
{
chinese.Speak();
}
}

结果:

[Head First设计模式]身边的设计模式——适配器模式

类适配器模式:

让上述例子变为类适配器模式,只需修改为:

 namespace Wolfy.适配器模式
{
public class Adapter:American,IChinese
{ }
}

总结

个人感觉,适配器模式,似乎在后期扩展程序的时候用。适配器模式,就像一个中间人,两个不兼容的家伙在一起,屁话没有,通过中间人,比较容易沟通。

参考:

《Head First 设计模式》