GoF设计模式:适配器模式(Adapter Pattern)—不兼容结构的协调


GoF设计模式:适配器模式(Adapter Pattern)—不兼容结构的协调

模式概述

模式定义

与电源适配器相似,在适配器模式中引入了一个被称为适配器(Adapter)的包装类,而它所包装的对象称为适配者(Adaptee),即被适配的类。适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。也就是说:当客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。因此,适配器让那些由于接口不兼容而不能交互的类可以一起工作。

适配器模式(Adapter Pattern): 将一个接口转换成期望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。

注意:在适配器模式定义中所提及的接口是指广义的接口,它可以表示一个方法或者方法的集合。

模式结构图

在适配器模式中,我们通过增加一个新的适配器类来解决接口不兼容的问题,使得原本没有任何关系的类可以协同工作。根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器类适配器两种,在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承关系。在实际开发中,对象适配器的使用频率更高,对象适配器模式结构如图所示

适配器模式(Adapter Pattern)——不兼容结构的协调

在对象适配器模式结构图中包含如下几个角色:

  • Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。
  • Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对AdapteeTarget进行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承(或者实现)Target并关联一个Adaptee对象使二者产生联系。
  • Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。

模式伪代码

对象适配器中,客户端需要调用request()方法,而适配者类Adaptee没有该方法,但是它所提供的specificRequest()方法却是客户端所需要的。为了使客户端能够使用适配者类,需要提供一个包装类Adapter,即适配器类。这个包装类包装了一个适配者的实例,从而将客户端与适配者衔接起来,在适配器的request()方法中调用适配者的specificRequest()方法。因为适配器类与适配者类是关联关系(也可称之为委派关系),所以这种适配器模式称为对象适配器模式。典型的对象适配器代码如下所示:

public class Adapter implements Target {
    // 维持一个对适配者对象的引用
    private Adaptee adaptee;

    // 构造注入适配者
    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public void request() {
        // 转发调用
        adaptee.specificRequest();
    }
}

类适配器,双向适配器,缺省适配器

类适配器

类适配器模式和对象适配器模式最大的区别在于适配器和适配者之间的关系不同,对象适配器模式中适配器和适配者之间是关联关系,而类适配器模式中适配器和适配者是继承关系

适配器模式(Adapter Pattern)——不兼容结构的协调

适配器类实现了抽象目标类接口Target,并继承了适配者类,在适配器类的request()方法中调用所继承的适配者类的specificRequest()方法,实现了适配。
典型代码实现如下:

public class Adapter extends Adaptee implements Target {
    @Override
    public void request() {
        specificRequest();
    }
}

由于JavaC#等语言不支持多重类继承,因此类适配器的使用受到很多限制,例如如果目标抽象类Target不是接口,而是一个类,就无法使用类适配器;此外,如果适配者Adaptee为最终(final)类,也无法使用类适配器。在Java等面向对象编程语言中,大部分情况下我们使用的是对象适配器,类适配器较少使用。

双向适配器

双向适配器: 在对象适配器的使用过程中,如果在适配器中同时包含对目标类和适配者类的引用,适配者可以通过它调用目标类中的方法,目标类也可以通过它调用适配者类中的方法,那么该适配器就是一个双向适配器。

适配器模式(Adapter Pattern)——不兼容结构的协调

典型代码实现如下:

public class Adapter implements Target, Adaptee {
    //同时维持对抽象目标类和适配者的引用
    private Target target;
    private Adaptee adaptee;

    public Adapter(Target target) {
        this.target = target;
    }

    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public void request() {
        adaptee.specificRequest();
    }

    @Override
    public void specificRequest() {
        target.request();
    }
}

在实际开发中,我们很少使用双向适配器。违背了单一职责原则,相当于一个适配器承担了两个适配器的职责。

缺省适配器

缺省适配器模式是适配器模式的一种变体,其应用也较为广泛。

缺省适配器模式(Default Adapter Pattern):当不需要实现一个接口所提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可以选择性地覆盖父类的某些方法来实现需求,它适用于不想使用一个接口中的所有方法的情况,又称为单接口适配器模式

适配器模式(Adapter Pattern)——不兼容结构的协调

典型代码实现如下:

public abstract class Adapter implements Target {
    @Override
    public void request1() {
        // 空实现,让具体实现类去有选择地实现
    }

    @Override
    public void request2() {
        // 空实现,让具体实现类去有选择地实现
    }

    @Override
    public void request3() {
        // 空实现,让具体实现类去有选择地实现
    }
}

public class ConcreteAdapter extends Adapter {
    // 维持一个对适配者对象的引用
    private Adaptee adaptee;

    // 构造注入适配者
    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public void request1() {
        // 只实现request1
        adaptee.specificRequest();
    }
}

模式应用

模式在JDK中的应用

在JDK中,IO类中也大量使用到了适配器模式。比如说StringReaderString适配到ReaderInputStreamReaderInputStream适配到Reader等等。

这里用StringReader来说明。这里的StringReader相当于上述的AdapterReader相当于上述的TargetString相当于上述的Adaptee

public class StringReader extends Reader {

   // 维持对adaptee对象的引用
    private String str;
    
    private int length;
    private int next = 0;
    private int mark = 0;

    /**
     * 构造注入一个String用于之后的read操作
     */
    public StringReader(String s) {
        this.str = s;
        this.length = s.length();
    }
  
  // 这里相当于是在做适配操作,转为目标对象所期望的请求
    public int read() throws IOException {
        synchronized (lock) {
            ensureOpen();
            if (next >= length)
                return -1;
            return str.charAt(next++);
        }
    }
}

模式在开源项目中的应用

其实不只是开源项目,我们自己写的项目很多地方都是隐含着适配器模式,只是有时候这种特性表现的不是很明显(因为我们很自然去使用),以至于我们都没有给类名命成XxxAdapter,比如说我们使用第三方库,第三方库某方法名太长或者参数过多,或者调用过于复杂了,我们可能会对第三方库再次做个封装,把适合自己项目当前业务逻辑的默认参数,默认实现补充完整,让其他地方很方便调用,举个具体例子,项目中可能经常会用到HttpClient,大多数情况下,对现有的HttpClient再次封装(比如client的创建、http响应结果的统一处理等等),封装成方便自己项目使用的SpecialHttpClient,如果你还想切换不同的底层HttpClient实现,还可以对SpecialHttpClient抽出来一个接口,通过不同的Adapter来注入不同的HttpClient(比如apache的HttpClientOkHttpClient、Spring的RestTemplate以及WebClient等等)来实现,这种很自然的思想 个人觉得本质上也用到了适配器模式,相当于是把第三方的HttpClient适配成了自己的SpecialHttpClient

当转换的源不是单一的时候,这种适配器思想就凸显出来了(对应上面的例子就是说 项目中需要同时用到apache的HttpClient、Spring的RestTemplate以及WebClient等)。

这里举个Spring中的例子。在SpringAOP中,由于Advisor需要的是MethodInterceptor对象,所以每一个Advisor中的Advice都要适配成对应的MethodInterceptor对象

public interface AdvisorAdapter {

	boolean supportsAdvice(Advice advice);
  
	MethodInterceptor getInterceptor(Advisor advisor);
}


class ThrowsAdviceAdapter implements AdvisorAdapter, Serializable{

	@Override
	public boolean supportsAdvice(Advice advice) {
		return (advice instanceof ThrowsAdvice);
	}

	@Override
	public MethodInterceptor getInterceptor(Advisor advisor) {
		return new ThrowsAdviceInterceptor(advisor.getAdvice());
	}

}

模式总结

适配器模式将现有接口转化为客户类所期望的接口,实现了对现有类的复用,它是一种使用频率非常高的设计模式,在软件开发中得以广泛应用。

主要优点

无论是对象适配器模式还是类适配器模式都具有如下优点:

(1) 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构,提高了扩展性,符合“开闭原则”
(2) 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。

具体来说,类适配器模式还有如下优点:

由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。

对象适配器模式还有如下优点:

(1) 一个对象适配器可以把多个不同的适配者适配到同一个目标;

(2) 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原则”,适配者的子类也可通过该适配器进行适配。

主要缺点

类适配器模式的缺点如下:

(1) 对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者;

(2) 适配者类不能为最终类,如在Java中不能为final类,C#中不能为sealed类;

(3) 在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。

对象适配器模式的缺点如下:

与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦(比如说适配者类中的某些方法是protected,而我们做适配的时候刚好需要用到)。如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。

适用场景

在以下情况下可以考虑使用适配器模式:

系统需要使用(复用)一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码等等,可使用适配器模式协调诸多不兼容结构的场景。

版权声明:本文为开发框架文库发布内容,转载请附上原文出处连接
C/S框架网
上一篇:GoF设计模式:单例模式(Singleton Pattern)—确保对象的唯一性
下一篇:GoF设计模式:迭代器模式(Iterator Pattern)—遍历聚合对象中的元素
评论列表

发表评论

评论内容
昵称:
关联文章

GoF设计模式适配器模式(Adapter Pattern)—兼容结构协调
GoF设计模式:中介者模式(Mediator Pattern)—协调多个对象之间交互
GoF设计模式:工厂模式(Factory Pattern)
GoF设计模式:组合模式(Composite Pattern)—树形结构处理
GoF设计模式:原型模式(Prototype Pattern)—对象克隆
GoF设计模式:代理模式(Proxy Pattern)—对象间接访问
GoF设计模式:访问者模式(Visitor Pattern)—操作复杂对象结构
GoF设计模式:装饰模式(Decorator Pattern)—扩展系统功能
GoF设计模式:职责链模式(Chain of Responsibility Pattern)—请求链式处理
GoF设计模式:享元模式(Flyweight Pattern)—实现对象复用
GoF设计模式:迭代器模式(Iterator Pattern)—遍历聚合对象中元素
GoF设计模式:单例模式(Singleton Pattern)—确保对象唯一性
GoF设计模式:外观模式(Facade Pattern)—提供统一入口
GoF设计模式:建造者模式(Builder Pattern)—复杂对象组装与创建
GoF设计模式:桥接模式(Bridge Pattern)—处理多维度变化
GoF设计模式:命令模式(Command Pattern)—请求发送者与接收者解耦
设计模式 - 外观模式应用(Façade Pattern)
C#设计模式应用-单件模式(Singleton Pattern)
vs 托管兼容模式支持“编辑并继续”
开发应用 - 策略模式(Strategy Pattern) 常用设计模式

热门标签
.NET5 .NET6 .NET7 APP Auth-软件授权注册系统 Axios B/S B/S开发框架 Bug Bug记录 C#加密解密 C#源码 C/S CHATGPT CMS系统 CodeGenerator CSFramework.DB CSFramework.EF CSFrameworkV1学习版 CSFrameworkV2标准版 CSFrameworkV3高级版 CSFrameworkV4企业版 CSFrameworkV5旗舰版 CSFrameworkV6.0 DAL数据访问层 Database datalock DbFramework Demo教学 Demo下载 DevExpress教程 DOM EF框架 Element-UI EntityFramework ERP ES6 Excel FastReport GIT HR IDatabase IIS JavaScript LINQ MES MiniFramework MIS NavBarControl Node.JS NPM OMS ORM PaaS POS Promise API Redis SAP SEO SQL SQLConnector TMS系统 Token令牌 VS2022 VSCode VUE WCF WebApi WebApi NETCore WebApi框架 WEB开发框架 Windows服务 Winform 开发框架 Winform 开发平台 WinFramework Workflow工作流 Workflow流程引擎 版本区别 报表 踩坑日记 操作手册 代码生成器 迭代开发记录 基础资料窗体 架构设计 角色权限 开发sce 开发技巧 开发教程 开发框架 开发平台 开发指南 客户案例 快速搭站系统 快速开发平台 秘钥 密钥 权限设计 软件报价 软件测试报告 软件简介 软件开发框架 软件开发平台 软件开发文档 软件体系架构 软件下载 软著证书 三层架构 设计模式 生成代码 实用小技巧 收钱音箱 数据锁 数据同步 微信小程序 未解决问题 文档下载 喜鹊ERP 喜鹊软件 系统对接 详细设计说明书 行政区域数据库 需求分析 疑难杂症 蝇量级框架 蝇量框架 用户管理 用户开发手册 用户控件 在线支付 纸箱ERP 智能语音收款机 自定义窗体 自定义组件 自动升级程序