【设计模式】C#实现适配器模式

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
云数据库 RDS SQL Server,基础系列 2核4GB
简介: 将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。适配器模式分为类适配器模式和对象适配器模式,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。

1、概述

背景

我们在买港版的电器的时候,肯定会伴随着要额外购买转换器,因为香港的插座跟内地是有区别的。我们需要一个转换头也就是适配器来适配内地的插座。

定义

将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

适配器模式分为类适配器模式和对象适配器模式,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。

结构

适配器模式(Adapter)包含以下主要角色:

  • 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
  • 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
  • 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。

2、实现思路

场景

以前开发的系统的关系型数据库接口存在满足新系统功能需求,但是非关系型数据库的接口(比如Redis)不满足新系统要求。此时,我们就需要加一个适配器类来适配非关系型数据库的接口。具体情况如下所示,

我们有两个关系型数据库类,MySQLClient和SqlserverClient,这两个类都有增删改成方法并且满足新系统调用要求;

    interface IDBClient
    {
        public void Add();
        public bool Delete();
        public void Update();
        public void Query();
    }
    
    public class MySQLClient : IDBClient
    {
        public void Add()
        {
            throw new NotImplementedException();
        }

        public bool Delete()
        {
            throw new NotImplementedException();
        }

        public void Query()
        {
            throw new NotImplementedException();
        }

        public void Update()
        {
            throw new NotImplementedException();
        }
    }
    
    public class SqlserverClient : IDBClient
    {
        public void Add()
        {
            throw new NotImplementedException();
        }

        public bool Delete()
        {
            throw new NotImplementedException();
        }

        public void Query()
        {
            throw new NotImplementedException();
        }

        public void Update()
        {
            throw new NotImplementedException();
        }
    }
    
    public class RedisClient
    {
        public void AddCache()
        { 
        
        }
        public bool DeleteCache() 
        {
            return true;
        }
        public void UpdateCache()
        { 
        
        }
        public void QueryCache()
        { 
        
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            #region MySQL
            {
                MySQLClient dBClient = new MySQLClient();
                dBClient.Add();
                dBClient.Delete();
                dBClient.Update();
                dBClient.Query();
            }
            #endregion

            #region Sqlserver
            {
                SqlserverClient dBClient = new SqlserverClient();
                dBClient.Add();
                dBClient.Delete();
                dBClient.Update();
                dBClient.Query();
            }
            #endregion

            #region Redis
            {
                RedisClient dBClient = new RedisClient();
                dBClient.Add();
                dBClient.Delete();
                dBClient.Update();
                dBClient.Query();
            }
            #endregion
        }
    }
AI 代码解读

此时,新系统要求通过Add、Delete、Update、Query方法进行增删改成操作时,编译器报错了,如下图所示。

可以看出RedisClient的方法并不满足我们的需求,并且RedisClient这个类是不允许修改的,因此我们需要在RedisClient类外面包一层来适配新系统。

类适配器
    /// <summary>
    /// 对象适配器
    /// </summary>
    public class RedisClientClassAdapter : RedisClient, IDBClient
    {
        public void Add()
        {
            base.AddCache();
        }

        public bool Delete()
        {
          return base.DeleteCache();
        }

        public void Query()
        {
            base.QueryCache();
        }

        public void Update()
        {
            base.UpdateCache();
        }
    }
AI 代码解读

顾名思义,类适配器就是新增一个类并让其继承需要适配的类。此时Program类可以改成如下,

class Program
    {
        static void Main(string[] args)
        {
            #region MySQL
            {
                IDBClient dBClient = new MySQLClient();
                dBClient.Add();
                dBClient.Delete();
                dBClient.Update();
                dBClient.Query();
            }
            #endregion

            #region Sqlserver
            {
                IDBClient dBClient = new SqlserverClient();
                dBClient.Add();
                dBClient.Delete();
                dBClient.Update();
                dBClient.Query();
            }
            #endregion

            #region Redis
            {
                IDBClient dBClient = new RedisClientAdapter();
                dBClient.Add();
                dBClient.Delete();
                dBClient.Update();
                dBClient.Query();
            }
            #endregion
        }
    }
AI 代码解读
对象适配器
    /// <summary>
    /// 对象适配器
    /// </summary>
    public class RedisClientObjectAdapter : IDBClient
    {
        private RedisClient _redisClient = new RedisClient();

        public void Add()
        {
            this._redisClient.AddCache();
        }

        public bool Delete()
        {
          return this._redisClient.DeleteCache();
        }

        public void Query()
        {
            this._redisClient.QueryCache();
        }

        public void Update()
        {
            this._redisClient.UpdateCache();
        }
    }
AI 代码解读

对象适配器不是继承一个类,而是在适配器类中创建一个对象,其实就是组合关系。

对比

接下来,我们对比一下类适配器和对象适配器,通过代码解释为什么前者类之间的耦合度比后者高。

我们知道继承的缺点是侵入性、不够灵活、高耦合:

  • 继承是侵入性的,只要继承就必须拥有父类的所有方法和属性,在一定程度上约束了子类,降低了代码的灵活性;
  • 增加了耦合,当父类的常量、变量或者方法被修改了,需要考虑子类的修改,所以一旦父类有了变动,很可能会造成
    非常糟糕的结果,要重构大量的代码。

这些问题,在类适配器中一样存在,我们可以看到类适配器由于继承了RedisClient,会把该类的方法也暴露出来,也就是其他开发同事可以调用AddCache等方法。如下所示,

            #region Redis
            {
                RedisClientClassAdapter dBClient = new RedisClientClassAdapter();
                dBClient.Add();
                dBClient.Delete();
                dBClient.Update();
                dBClient.Query();
                dBClient.AddCache();
                dBClient.DeleteCache();
                dBClient.UpdateCache();
                dBClient.QueryCache();
            }
            #endregion
AI 代码解读

当然这个问题我们可以通过如下方法来回避掉这个问题,

另外,RedisClient升级了,我们就要去修改对应的RedisClientClassAdapter;

对象适配器可以为多个对象服务(通过构造函数传入);

因此,我们说组合优于继承

3、应用场景

  • 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
  • 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
「全网最细 + 实战源码案例」设计模式——适配器模式
适配器模式(Adapter Pattern)是一种结构型设计模式,通过引入适配器类将一个类的接口转换为客户端期望的另一个接口,使原本因接口不兼容而无法协作的类能够协同工作。适配器模式分为类适配器和对象适配器两种,前者通过多重继承实现,后者通过组合方式实现,更常用。该模式适用于遗留系统改造、接口转换和第三方库集成等场景,能提高代码复用性和灵活性,但也可能增加代码复杂性和性能开销。
75 28
前端必须掌握的设计模式——适配器模式
适配器模式是一种结构型设计模式,用于使接口不兼容的对象能够相互合作。通过在客户端和系统之间引入一个“中间层”适配器,将不同类型的输入数据转换为系统能处理的标准格式,减轻系统的负担,提高扩展性和可维护性。例如,MacBook的扩展坞将多种接口(如HDMI、USB)转换为Type-C接口,实现多接口兼容。
C# 一分钟浅谈:设计模式之单例模式
【10月更文挑战第9天】单例模式是软件开发中最常用的设计模式之一,旨在确保一个类只有一个实例,并提供一个全局访问点。本文介绍了单例模式的基本概念、实现方式(包括饿汉式、懒汉式和使用 `Lazy&lt;T&gt;` 的方法)、常见问题(如多线程和序列化问题)及其解决方案,并通过代码示例详细说明了这些内容。希望本文能帮助你在实际开发中更好地应用单例模式,提高代码质量和可维护性。
182 1
Java设计模式-适配器模式(8)
Java设计模式-适配器模式(8)
Java设计模式之适配器模式
这篇文章详细讲解了Java设计模式中的适配器模式,包括其应用场景、实现方式及代码示例。
84 0
设计模式--适配器模式 Adapter Pattern
这篇文章介绍了适配器模式,包括其基本介绍、工作原理以及类适配器模式、对象适配器模式和接口适配器模式三种实现方式。
C#设计模式之策略模式
C#设计模式之策略模式
106 19
【六】设计模式~~~结构型模式~~~适配器模式(Java)
文章详细介绍了适配器模式(Adapter Pattern),这是一种结构型设计模式,用于将一个类的接口转换成客户期望的另一个接口,使原本不兼容的接口能够一起工作,提高了类的复用性和系统的灵活性。通过对象适配器和类适配器两种实现方式,展示了适配器模式的代码应用,并讨论了其优点、缺点以及适用场景。
C#设计模式之单例模式
C#设计模式之单例模式
68 3
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等