zl程序教程

您现在的位置是:首页 >  后端

当前栏目

【设计模式】适配器模式

模式设计模式 适配器
2023-09-14 09:16:26 时间

【设计模式】适配器模式

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
        }
    }

此时,新系统要求通过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();
        }
    }

顾名思义,类适配器就是新增一个类并让其继承需要适配的类。此时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
        }
    }
对象适配器
    /// <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();
        }
    }

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

对比

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

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

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

这些问题,在类适配器中一样存在,我们可以看到类适配器由于继承了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

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

在这里插入图片描述

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

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

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

3、应用场景

  • 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
  • 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。