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

总目录


前言

在实际的开发过程中,由于需求的变化和扩展,我们的代码也需要做相应的扩展。想象这样一个场景,原项目中接口返回的数据是XML格式的数据,但现在来了一个新客户,它期望接口返回的数据类型为json格式的。想要实现要么就是改原有接口,但这样就违反了开闭原则,容易出现未知bug,影响到老客户的正常使用。而如果写一个适配器类也就是转换类(第三方类),将原本返回的XML格式数据转换成json格式数据,而具体数据是怎么来的则直接用原有接口方法就可以,这就是适配器模式。


1 基本介绍

  1. 定义:将一个类的接口转换成客户端所期望的另一个接口,使得原本由于接口不兼容而无法协同工作的类能够一起工作。
  2. 适配器模式有类的适配器模式和对象的适配器模式两种形式
  3. 适配器模式(Apapter Pattern)是一种结构型设计模式,用来解决现有对象与客户端期待接口不一致的问题
  4. 适配器模式中的角色:
    • 目标角色(Target):描述了其他类与客户端代码合作时必须遵循的协议。
    • 客户角色(Client):与符合Target接口的对象协同。
    • 被适配(服务类,功能类)(Adaptee):定义一个已经存在并已经使用的接口,这个接口需要适配。 客户端与其接口不兼容, 因此无法直接调用其功能。
    • 适配器(Adapter) :适配器模式的核心。适配器接受客户端通过适配器接口发起的调用,同时根据其内在逻辑调用对应服务类。客户端代码只需通过接口与适配器交互即可, 无需与具体的服务类耦合。

现实生活中空调插头一般都是三头的,但如果家里只有两孔插座,那必然是插不进去的。而如果提供一个拥有三孔插座和两头插头的转换器的话,那空调可以先插在这个转换器上,然后这个转换器再插在插座上就可以了。本质并没有变,只是将二孔插座包装了一下,向外界提供了一个三孔插座的外观以供客户使用。

适配的本质就是转换,将不满足使用条件的东西通过第三方类进行加工处理成可使用的东西。

2 使用场景

  • 系统需要复用现有类,但是接口又与复用环境要求不一致的情况。
  • 旧系统与新系统的兼容:可以使新系统能够无缝地与老旧系统进行通信。
  • 第三方组件的集成:适配器可以将第三方组件的接口转换为符合我们系统需求的接口形式,从而能够顺利地集成到我们的系统中。
  • 多个类库之间的互操作:适配器模式可以起到桥梁的作用。

3 实现方式

1. 类适配器

以常见的数据库辅助接口为例

csharp 复制代码
    //数据库辅助接口【目标角色】
    public interface IDbHelper
    {
        //负责执行数据查询 Query
        void ExecDQL(string sql);
        //负责执行数据操作 Create,Delete,Update
        void ExecDML(string sql);
    }

    // SqlServer的辅助类 实现
    public class SqlServerHelper : IDbHelper
    {
        public void ExecDML(string sql)
        {
            Console.WriteLine($"SqlServerHelper执行了【操作】sql:{sql}");
        }

        public void ExecDQL(string sql)
        {
            Console.WriteLine($"SqlServerHelper执行了【查询】sql:{sql}");
        }
    }

    // SqlServer的辅助类 实现
    public class MySqlHelper : IDbHelper
    {
        public void ExecDML(string sql)
        {
            Console.WriteLine($"MySqlHelper执行了【操作】sql:{sql}");
        }

        public void ExecDQL(string sql)
        {
            Console.WriteLine($"MySqlHelper执行了【查询】sql:{sql}");
        }
    }

    //业务扩展了,关系型数据库已经不满足于现在的业务了
    //需要给系统增加缓存,用到了非关系型数据库
    //非关系数据库辅助类 【被适配者】
    public class NoSqlHelper
    {
        //非关系数据库【特有的】操作数据方法
        public void ExecNoSqlDML(string str)
        {
            Console.WriteLine($"NoSqlHelper执行了【操作】:{str}");
        }

        //非关系数据库【特有的】查询数据方法
        public void ExecNoSqlDQL(string str)
        {
            Console.WriteLine($"NoSqlHelper执行了【查询】:{str}");
        }
    }

    //适配器类
    public class NoSqlHelperAdapter : NoSqlHelper, IDbHelper
    {
    	//实际上调用非关系型数据库的数据操作方法
        public void ExecDML(string sql)
        {
            base.ExecNoSqlDML(sql);
        }

        public void ExecDQL(string sql)
        {
            base.ExecNoSqlDQL(sql);
        }
    }

客户端调用

csharp 复制代码
        public static void Main(string[] args)
        {
            //客户端可以通过适配器来使用IDbHelper这个数据库辅助类
            //因为通过NoSqlHelperAdapter 适配器类,已经将其包装成了IDbHelper
            IDbHelper dbHelper = new NoSqlHelperAdapter();
            dbHelper.ExecDML("...");

            Console.ReadLine();
        }

从实例中可以看出,类适配器主要是用继承来实现的,但如果有很多个类进行适配,这个方式就不支持了。

2. 对象适配器

相对于类适配器,这部分代码不变

csharp 复制代码
//数据库辅助类
    public interface IDbHelper
    {
        //负责执行数据查询 Query
        void ExecDQL(string sql);
        //负责执行数据操作 Create,Delete,Update
        void ExecDML(string sql);
    }

    // SqlServer的辅助类 实现
    public class SqlServerHelper : IDbHelper
    {
        public void ExecDML(string sql)
        {
            Console.WriteLine($"SqlServerHelper执行了【操作】sql:{sql}");
        }

        public void ExecDQL(string sql)
        {
            Console.WriteLine($"SqlServerHelper执行了【查询】sql:{sql}");
        }
    }

    // SqlServer的辅助类 实现
    public class MySqlHelper : IDbHelper
    {
        public void ExecDML(string sql)
        {
            Console.WriteLine($"MySqlHelper执行了【操作】sql:{sql}");
        }

        public void ExecDQL(string sql)
        {
            Console.WriteLine($"MySqlHelper执行了【查询】sql:{sql}");
        }
    }

    //业务扩展了,关系型数据库已经不满足于现在的业务了
    //需要给系统增加缓存,用到了非关系型数据库
    //非关系数据库辅助类
    public class NoSqlHelper
    {
        //非关系数据库操作数据
        public void ExecNoSqlDML(string str)
        {
            Console.WriteLine($"NoSqlHelper执行了【操作】:{str}");
        }

        //非关系数据库查询数据
        public void ExecNoSqlDQL(string str)
        {
            Console.WriteLine($"NoSqlHelper执行了【查询】:{str}");
        }
    }

仅仅是适配类的代码发生改变

csharp 复制代码
    //适配器
    public class NoSqlHelperAdapter : IDbHelper
    {
    	 //引用非关系数据库辅助类 的实例
        public NoSqlHelper noSqlHelper = new NoSqlHelper();
        public void ExecDML(string sql)
        {
        	//通过实例调用相关数据操作的方法
            noSqlHelper.ExecNoSqlDML(sql);
        }

        public void ExecDQL(string sql)
        {
            noSqlHelper.ExecNoSqlDQL(sql);
        }
    }

从实例中可以看出,对象适配器其实就是在适配器类中创建了一个被适配者的实例,从而将两者联系在一起。这种方式采用 "对象组合"的方式,更符合松耦合。

从两个案例上知道,适配器模式并不是项目一开始就会用到的,而是随着需求的变更和扩展,我们不得已才开发一个适配器类,将新增的功能通过适配器类 "包一层" 的方式转换为我们原有对外提供的接口,使得我们可以在不修改原有代码的基础上来复用现有类,很好地符合 "开闭原则"

4 优缺点分析

类的适配器模式:

  • 优点:
    • 可以在不修改原有代码的基础上来复用现有类,很好地符合 "开闭原则"
    • 可以重新定义Adaptee(被适配的类)的部分行为,因为在类适配器模式中,Adapter是Adaptee的子类
    • 仅仅引入一个对象,并不需要额外的字段来引用Adaptee实例(这个即是优点也是缺点)。
  • 缺点:
    • 采用了 "多继承"的实现方式,带来了不良的高耦合。

对象的适配器模式:

  • 优点:
    • 可以在不修改原有代码的基础上来复用现有类,很好地符合 "开闭原则"(这点是两种实现方式都具有的)
    • 采用 "对象组合"的方式,更符合松耦合。
  • 缺点:
    • 使得重定义Adaptee(被适配的类)的行为较困难,这就需要生成Adaptee的子类并且使得Adapter引用这个子类而不是引用Adaptee本身。

结语

希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。


参考资料:
c#中适配器模式详解
C#设计模式(7)-适配器模式

相关推荐
霁月风7 小时前
设计模式——适配器模式
c++·适配器模式
WaaTong8 小时前
《重学Java设计模式》之 单例模式
java·单例模式·设计模式
小码编匠10 小时前
一款 C# 编写的神经网络计算图框架
后端·神经网络·c#
WaaTong10 小时前
《重学Java设计模式》之 原型模式
java·设计模式·原型模式
霁月风10 小时前
设计模式——观察者模式
c++·观察者模式·设计模式
暗黑起源喵12 小时前
设计模式-工厂设计模式
java·开发语言·设计模式
Envyᥫᩣ13 小时前
C#语言:从入门到精通
开发语言·c#
IT技术分享社区19 小时前
C#实战:使用腾讯云识别服务轻松提取火车票信息
开发语言·c#·云计算·腾讯云·共识算法
wrx繁星点点20 小时前
状态模式(State Pattern)详解
java·开发语言·ui·设计模式·状态模式