设计模式(三)-结构型模式(1)-适配器模式

一、为何需要适配器模式(Adapter)?

在软件设计中,某个模块里有很多公用的功能接口,其中有些公用接口需要用到不同的类当中时,会出现接口不兼容的问题。因为这些不同的类对这个相同任务的接口,都有各自代码逻辑的要求。于是每当客户对这个接口提出新需求,就会再创建一个类,再把所有方法实现一遍,就显得代码很臃肿。为了解决这个问题,只改写需要适配的某个功能,就用到了适配器模式。

比如在文件操作的模块中,都有获取文件属性、读写文件操作等共同的功能接口。在原需求里,已存在保存 txt 文件的功能。当客户有新需求时,需要扩展保存图片文件的功能。这就出现了一个问题:保存 txt 文件和保存图片虽然都是保存操作的任务,但是实现的代码逻辑并不相同。

c 复制代码
       //保存txt文件
        public void SaveFile()
        {
            StreamWriter sw = new StreamWriter("test.txt");
            sw.Write("HelloWorld!");
            sw.Dispose();
        }

        //保存图片文件
        public void SaveFile()
        {
            //data 里随便写的数据,不要太认真
            byte[] data = { 11,22,33,44,55};
            MemoryStream ms = new MemoryStream(data);

            Image img = Image.FromStream(ms);
            img.Save("test.jpeg", ImageFormat.Jpeg);
            ms.Dispose();
        }

//保存任务相同:SaveFile。但是实现的代码逻辑不同

特点:

将一个类的接口转换成客户希望的另外一个接口,以解决接口不兼容的问题。(这里的接口不是指 C# 中的 interface,而是一个功能接口)

结构:

目标(Target):定义 Client 需要使用的功能接口。

被适配(Adaptee):定义一些在 Target 已经存在且需要适配的功能接口。

适配器(Adapter):主要负责将 Adaptee 功能接口转换为跟 Target 匹配的功能接口。

客户(Client):使用做了兼容处理的功能接口。

适合应用场景特点:

  • 这个接口具有相同的任务。(比如所有类型的文件共有读写保存的功能接口。)
  • 需要适配相同任务但工作方式不同的接口。(比如不同类型文件保存的方式不同。)

主要两种适配器模式:

  • (1)对象适配器模式
    (处理指定某些功能接口不兼容的问题;Target 派生给 Adapter,Adapter 包装一个需要 Adaptee 的类实例。注重对象组合关系)
  • (2)类适配器模式
    (处理指定某些功能接口不兼容的问题;Target 派生给 Adapter, Adaptee 也派生给 Adapter,特点为多重继承。注重类之间的继承关系)

注:==在工作中推荐使用对象适配器模式。==因为如果使用类适配器,类继承关系就会使得代码结构紧耦合,修改某个基类时就会影响子类。而对象适配不仅满足用户期待的需求,还能降低代码之间的耦合性。

对象适配模式:

类适配模式:

二、例子

需求:

在迭代版本中,客户希望在保存文本文件功能的基础上,还想要增加对一个图片文件进行保存的功能。并且在将来,可能还会扩展对其他类型文件进行保存的功能。

1、对象适配器模式:

c 复制代码
    //目标类:接口、抽象类、具体类都可作为目标
    public class FileOperateTarget
    {
        //保存文本文件
        public virtual void Request()
        {
            Console.WriteLine("Save a string content as the txt,xml,ini...");
        }

        //其他所有要实现的方法
        //(比如获取文件属性的方法,设置默认打开方式的方法等,都是任何类型文件共有的方法)...

        //如果存在代码逻辑不同的方法,则可由适配器模式进行处理。
        //(比如在此例子中,保存文件的方法)
    }

    //被适配类:保存图片文件
    public class ImageFileAdaptee
    {
        public virtual void SpecificRequest()
        {
            Console.WriteLine("Save a Image content as the Img File.");
        }
    }

    //适配器类:主要负责将 Adaptee 类对象进行组合来适配共同方法。
    public class ImageFileAdapter : FileOperateTarget
    {
        //包装一个被适配类的对象(Adaptee 对象)
        private ImageFileAdaptee imageFileAdaptee = new ImageFileAdaptee();
        public override void Request()
        {
            //适配功能的转换工作,把源接口 Request 转换成目标接口 SpecificRequest
            imageFileAdaptee.SpecificRequest();
        }
    }

    //Client
    class Program
    {
        static void Main(string[] args)
        {
            FileOperateTarget target = new ImageFileAdapter();
            target.Request();
            Console.ReadLine();
        }
    }

2、类适配器模式:

c 复制代码
    //目标接口:由于在C# 中类只支持单继承,而接口可支持多重继承。所以目标只能是接口 
    public interface IFileOperateTarget
    {
        void Request();
    }

    //被适配类:保存图片文件
    public class ImageFileAdaptee
    {
        public virtual void SpecificRequest()
        {
            Console.WriteLine("Save a Image content as the Img File.");
        }
    }

    //适配器类:主要继承 Adaptee 类 和 Target 类,实现目标接口的转换。
    public class ImageFileAdapter : ImageFileAdaptee, IFileOperateTarget
    {
        public void Request()
        {
            //适配功能的转换工作
            this.SpecificRequest();
        }
    }

    //Client
    class Program
    {
        static void Main(string[] args)
        {
            IFileOperateTarget target = new ImageFileAdapter();
            target.Request();
            Console.ReadLine();
        }
    }

三、缺省适配器模式

  • 特殊的适配器模式,不是主要的模式;一般缺少 Adaptee 结构。
  • 专门处理指定某些功能接口不兼容的问题,而其他所有功能接口都出于未处理状态,即空方法。

缺省适配器模式:

c 复制代码
    //目标接口
    public interface IFileOperateTarget
    {

        void Request();
        //其他抽象方法...
        void ReadFile();
        void GetFileInfo();
    }
   
    //适配器类:实现接口中抽象方法为空方法
    public class DefaultAdapter : IFileOperateTarget
    {
        public virtual void Request() { }
        public virtual void ReadFile() { }
        public virtual void GetFileInfo() { }
    }

    //Client
    public class ClientImage : DefaultAdapter
    {
        //只使用这个功能
        public override void Request()
        {
            Console.WriteLine("Save a Image content as the Img File.");
        }

        
    }

    //Client:以后有新需求时,可扩展此功能的代码逻辑
    public class ClientWord : DefaultAdapter
    {
        public override void Request()
        {
            Console.WriteLine("Save a Word content as the Word File.");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ClientImage clientImage = new ClientImage();
            clientImage.Request();

            ClientWord clientWord = new ClientWord();
            clientWord.Request();

            Console.ReadLine();
        }
    }
相关推荐
芒果披萨11 分钟前
El表达式和JSTL
java·el
duration~1 小时前
Maven随笔
java·maven
zmgst1 小时前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql
跃ZHD1 小时前
前后端分离,Jackson,Long精度丢失
java
blammmp1 小时前
Java:数据结构-枚举
java·开发语言·数据结构
暗黑起源喵2 小时前
设计模式-工厂设计模式
java·开发语言·设计模式
WaaTong2 小时前
Java反射
java·开发语言·反射
九圣残炎2 小时前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode
wclass-zhengge2 小时前
Netty篇(入门编程)
java·linux·服务器
Re.不晚3 小时前
Java入门15——抽象类
java·开发语言·学习·算法·intellij-idea