C#设计模式 之 适配器模式

举报
陈言必行 发表于 2021/08/23 22:41:31 2021/08/23
【摘要】 适配器模式是一种结构型设计模式, 它能使接口不兼容的对象能够相互合作。

别名:封装器模式、Adapter

一,意图

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


二,动机

   在程序设计中,由于应用环境的变化,常常需要将“一些现存的对象”放在新的环境中应用,但是新环境要求的接口是这些现存对象所不满足的。

   有时为复用而设计的工具箱类不能够被复用的原因仅仅是因为它的接口与专业应用领域所需要的接口不适配。

问题来了:
  如何应对这种“迁移的变化”?如何既能引用现有对象的良好实现,同时又能满足新的应用环境所要求的接口?

举例理解:
  我们要做一个数据图表展示系统,使用的是XML格式的文件。后来由于市场需求的增加,我们需要引用第三方库来做数据分析,而此时第三方库使用的是JSON格式,那么此时我们怎么办?

有两种方式:

  1. 修改第三方库使其支持XML文件。(若没有第三方库源码,好像就没办法了;或者即便是有源码去)
  2. 将XML格式文件转换为JSON格式,提供个第三方库引用。(实现JSON和XML的相互转换)

很明显方法2实现起来要比1各个方面都好很多(实现简单,提升工作效率,保证原有逻辑不变,使主逻辑便于后续维护),这便是适配器模式。

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

适配器模式本身要求我们尽可能地使用“面向接口的编程方式”,这样才能在后期很方便地适配。


三,结构

1.1

  1. 客户端 (Client) 是包含当前程序业务逻辑的类。
  2. 客户端接口 (Client Interface) 描述了其他类与客户端代码合作时必须遵循的协议。
  3. 服务 (Service) 中有一些功能类 (通常来自第三方或遗留系统)。 客户端与其接口不兼容, 因此无法直接调用其功能。
  4. 适配器 (Adapter) 是一个可以同时与客户端和服务交互的类: 它在实现客户端接口的同时封装了服务对象。 适配器接受客户端通过适配器接口发起的调用, 并将其转换为适用于被封装服务对象的调用。
  5. 客户端代码只需通过接口与适配器交互即可, 无需与具体的适配器类耦合。 因此, 你可以向程序中添加新类型的适配器而无需修改已有代码。 这在服务类的接口被更改或替换时很有用: 你无需修改客户端代码就可以创建新的适配器类。

四,优缺点

优点:

  • 单一职责原则:你可以将接口或数据转换代码从程序主要业务逻辑中分离。
  • 开闭原则: 只要客户端代码通过客户端接口与适配器进行交互, 你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器。

缺点:

  • 代码整体复杂度增加 :因为需要新增一系列接口和类。 有时直接更改服务类使其与其他代码兼容会更简单。

五,应用场景

适用性:

  • 当你希望使用某个类, 但是其接口与其他代码不兼容时, 可以使用适配器模式。
    适配器模式允许你创建一个中间层类, 其可作为代码与遗留类、 第三方类或提供怪异接口的类之间的转换器。

  • 当需要创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类。(即那些接口可能不一定兼容的类)

  • 确保至少有两个类的接口不兼容:
    一个无法修改 (通常是第三方、 遗留系统或者存在众多已有依赖的类) 的功能性服务类。
    一个或多个将受益于使用服务类的客户端类。


六,代码实现

实现方式:

  1. 声明客户端接口, 描述客户端如何与服务交互。
  2. 创建遵循客户端接口的适配器类。 所有方法暂时都为空。
  3. 在适配器类中添加一个成员变量用于保存对于服务对象的引用。 通常情况下会通过构造函数对该成员变量进行初始化, 但有时在调用其方法时将该变量传递给适配器会更方便。
  4. 依次实现适配器类客户端接口的所有方法。 适配器会将实际工作委派给服务对象, 自身只负责接口或数据格式的转换。
  5. 客户端必须通过客户端接口使用适配器。 这样一来,就可以在不影响客户端代码的情况下修改或扩展适配器。

示例代码:

class Project
{
    static void Main(string[] args)
    {
        Console.WriteLine("正常使用:");
        Shoot shoot = new Shoot();
        shoot.FireBullet();
        Console.WriteLine();

        Console.WriteLine("对象适配:");
        Cannon cannon = new Cannon(shoot);
        cannon.FireGun();
        Console.WriteLine();


        Console.WriteLine("类适配器:");
        CannonClass cannonClass = new CannonClass();
        cannonClass.FireGun();

        Console.ReadKey();
    }

}

/// <summary>
/// 发射类 -- 被修饰的类 (被希望复用的现存的类)
/// 包含对发射子弹的封装:
/// 比如:发射速度,生效距离
/// </summary>
class Shoot
{
    public void FireBullet()
    {
        Console.WriteLine(" --- 被适配类 发射子弹 --- ");
    }
}


/// <summary>
/// 后续添加的接口 -- 需要适配的目标接口
/// </summary>
interface ICannon
{
    void FireGun();
}

/// <summary>
/// 对象适配器 -- 实现目标接口,适配发射类
/// </summary>
class Cannon : ICannon
{
    private Shoot _Shoot;

    //public Cannon()
    //{
    //    this._Shoot = new Shoot();
    //}

    public Cannon(Shoot shoot)
    {
        this._Shoot = shoot;
    }

    public void FireGun()
    {
        this._Shoot.FireBullet();
        //使用一些Shoot的属性,来确定要发射什么样的炮弹
        Console.WriteLine(" --- 对象适配 发射炮弹 --- ");
    }
}

/// <summary>
/// 类适配器 -- 实现目标接口,适配发射类
/// 只能借用继承来实现,不能多继承而且是紧耦合 (很少使用)
/// </summary>
class CannonClass : Shoot, ICannon
{
    public void FireGun()
    {
        base.FireBullet();
        //使用一些Shoot的属性,来确定要发射什么样的炮弹
        Console.WriteLine(" --- 类适配器 发射炮弹 --- ");
    }
}

测试结果


设计模式系列博文示例代码工程:链接



【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。