适配器模式——不兼容结构的协调

1、简介

1.1、概述

有的笔记本电脑的工作电压是20V,而我国的家庭用电是220V,如何让20V的笔记本电脑能够在220V的电压下工作?答案是引入一个电源适配器(AC Adapter),俗称充电器/变压器。有了这个电源适配器,生活用电和笔记本电脑即可兼容,如下图所示:
在这里插入图片描述

在软件开发中,有时也存在类似这种不兼容的情况,也可以像引入一个电源适配器一样引入一个被称为适配器的角色来协调这些存在不兼容的结构,这种设计方案即为适配器模式。

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

1.2、定义

适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
:在适配器模式定义中所提及的接口是指广义的接口,它可以表示一个方法或者一组方法的集合。

2、解析

在适配器模式中,通过增加一个新的适配器类来解决接口不兼容的问题,使得原本没有任何关系的类可以协同工作。根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器模式和类适配器模式两种。在对象适配器模式中,适配器与适配者之间是关联关系;

2.1、对象适配模式

在类适配器模式中,适配器与适配者之间是继承(或实现)关系。在实际开发中,对象适配器模式的使用频率更高,其结构如下图所示:

在这里插入图片描述

可以看出,在对象适配器模式结构图中包含以下3个角色:

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

代码示例:

/**
 * 适配者类
 */
public class Adaptee {
    public String specificRequest(String org) {

        return org;
    }
}

/**
 * 适配器类
 */
public class Adapter extends Target{

    // 持有一个对象适配者类对象的引用
    private Adaptee adaptee;

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


    public String request(String org) {
        // 转发调用
        return adaptee.specificRequest(org);
    }
}
/**
 * 目标抽象类
 */
public class Target {

    public String request(String org) {

        return org;
    }

}
/**
 * 客户端
 */
public class Client {
    public static void main(String[] args) {
        Target target = new Target();
        target.request("1024");
    }
}

2.2、类适配器模式

类适配器模式与对象适配器模式最大的区别在于其适配器和适配者之间的关系是继承关系。类适配器模式结构如下图所示。
在这里插入图片描述
所示的类适配器模式结构图,适配器类实现了抽象目标类接口Target,并继承了适配者类。在适配器类的request()方法中调用所继承的适配者类的specificRequest()方法,实现了适配。典型的类适配器模式代码如下:

/**
 * 适配器类
 */
public class Adapter extends Adaptee implements Target {

    public String request(String org) {
        // 转发调用
        return super.specificRequest(org);
    }
}

/**
 * 抽象目标类接口
 */
public interface Target {

    String request(String org);

}

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

2.3、双向适配器模式

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

在这里插入图片描述

双向适配器模式的实现较为复杂,其典型代码如下:

/**
 * 适配器类
 */
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 String specificRequest(String org) {
        return adaptee.specificRequest(org);
    }

    @Override
    public String request(String org) {
        return target.request(org);
    }
}

2.4、缺省适配器模式

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

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

在这里插入图片描述
可以看出,在缺省适配器模式中,包含以下3个角色:

  • ServiceInterface(适配者接口):它是一个接口,通常在该接口中声明了大量的方法。
  • AbstractServiceClass(缺省适配器类):它是缺省适配器模式的核心类,使用空方法的形式实现了在ServiceInterface接口中声明的方法。通常将它定义为抽象类,因为对它进行实例化没有任何意义。
  • ConcreteServiceClass(具体业务类):它是缺省适配器类的子类,在没有引入适配器之前,它需要实现适配者接口,因此需要实现在适配者接口中定义的所有方法,而对于一些无须使用的方法也不得不提供空实现。在有了缺省适配器模式之后,可以直接继承该适配器类,根据需要有选择性地覆盖在适配器类中定义的方法。

3、总结

适配器模式将现有接口转化为客户类所期望的接口,实现了对现有类的复用。它是一种使用频率非常高的设计模式,在软件开发中得以广泛应用,在Spring等开源框架、驱动程序设计(例如JDBC中的数据库驱动程序)中也使用了适配器模式。

3.1、优点

  1. 将目标类和适配者类解耦。通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。
  2. 增加了类的透明性和复用性。将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者类的复用性,同一个适配者类可以在多个不同的系统中复用。
  3. 灵活性和扩展性都非常好。通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合开闭原则。具体来说,类适配器模式还有这样的优点:由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。

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

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

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

3.2、缺点

  1. 对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者。
  2. 适配者类不能为最终类,例如在Java中不能为final类,C#中不能为sealed类。
  3. 在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。

对象适配器模式的缺点是:与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,在子类中将适配者类的方法置换掉,然后再把适配者类的子类当作真正的适配者进行适配,实现过程较为复杂。

4、适用场景

  1. 系统需要使用一些现有的类,而这些类的接口(例如方法名)不符合系统的需要,甚至没有这些类的源代码。
  2. 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的类,包括一些可能在将来引进的类一起工作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/53652.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[PAT乙级] 1029 旧键盘 C++实现

题目描述: 旧键盘上坏了几个键,于是在敲一段文字的时候,对应的字符就不会出现。现在给出应该输入的一段文字、以及实际被输入的文字,请你列出肯定坏掉的那些键。 输入格式: 输入在 2 行中分别给出应该输入的文字、以…

Java生成二维码——附Utils工具类

参加2023年的计算机设计大赛国赛,拿到了一等奖。 现在将项目中的工具类代码剥离出来,方便之后项目开发中复用。 实现效果: 代码实现: import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; import c…

《MySQL 实战 45 讲》课程学习笔记(二)

日志系统:一条 SQL 更新语句是如何执行的? 与查询流程不一样的是,更新流程还涉及两个重要的日志模块:redo log(重做日志)和 binlog(归档日志)。 重要的日志模块:redo l…

怎么通过通过 p 名称空间配置 bean以及怎么去引用/注入其它 bean 对象--ref和怎么去引用/注入内部 bean 对象-内部 bean 对象

😀前言 本章是spring基于XML 配置bean系类中第2篇讲解怎么通过通过 p 名称空间配置 bean以及怎么去引用/注入其它 bean 对象–ref和怎么去引用/注入内部 bean 对象 🏠个人主页:尘觉主页 🧑个人简介:大家好&#xff0…

每日一题——只出现一次的数字(II)

只出现一次的数字——II 题目链接 注:本题的解法建立在位运算之上,如果对位运算不太了解,建议先看看👉位运算详解 思路 可能有小伙伴做了只出现一次的数字——I后认为这题也可以用异或运算来解决,但是我们需要注意到…

力扣468 验证IP地址

ipv4地址:1.必须是四个非空子串 2.每个非空子串不含前导零 3.子串里字符只能是0~255 ipv6地址:1.必须是八个非空子串 2。每段非空串得长度是否在1~4之间,且不含0-9,a-f,A-F之外得字符。 3.同时0-9也不允许含前导零 cl…

543. 二叉树的直径

题目 题解一 遍历每一个节点,以每一个节点为中心点计算最长路径(左子树边长右子树边长),更新全局变量max。 class Solution {int maxd0;public int diameterOfBinaryTree(TreeNode root) {depth(root);return maxd;}public int …

C# 翻转二叉树

226 翻转二叉树 给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。 示例 1: 输入:root [4,2,7,1,3,6,9] 输出:[4,7,2,9,6,3,1] 示例 2: 输入:root [2,1,3] 输出:…

打造交流利器:PHP留言板功能详解与实践

🏆作者简介,黑夜开发者,全栈领域新星创作者✌,2023年6月csdn上海赛道top4。多年电商行业从业经验,对系统架构,数据分析处理等大规模应用场景有丰富经验。 🏆本文已收录于PHP专栏:PHP…

Linux操作系统学习,Linux基础命令大全

目录 第一章、Linux简介和安装1.1)Linux简介和分类1.2)安装VMware虚拟机,在虚拟机中安装CentOS 7 第二章、虚拟机中Linux的IP地址配置详解2.1)什么是IP地址,如何查看2.2)虚拟机NAT模式中Linux的IP地址设置有…

MyBatis缓存-提高检索效率的利器--二级缓存

文章目录 缓存-提高检索效率的利器缓存-官方文档二级缓存基本介绍二级缓存原理图 二级缓存快速入门快速入门注意事项和使用陷阱理解二级缓存策略的参数 四大策略如何禁用二级缓存mybatis 刷新二级缓存的设置 缓存-提高检索效率的利器 缓存-官方文档 文档地址: https://mybati…

Java面试准备篇:全面了解面试流程与常见问题

文章目录 1.1 Java面试概述1.2 面试流程和注意事项1.3 自我介绍及项目介绍1.4 常见面试问题 在现代职场中,面试是求职过程中至关重要的一环,特别是对于Java开发者而言。为了帮助广大Java开发者更好地应对面试,本文将提供一份全面的Java面试准…

【Golang 接口自动化02】使用标准库net/http发送Post请求

目录 写在前面 发送Post请求 示例代码 源码分析 Post请求参数解析 响应数据解析 验证 发送Json/XMl Json请求示例代码 xml请求示例代码 总结 资料获取方法 写在前面 上一篇我们介绍了使用 net/http 发送get请求,因为考虑到篇幅问题,把Post单…

JVM类加载器的作用和层次结构

类加载器的作用 1)通过一个类的全限定名来获取定义此类的二进制字节流。 2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。 3)在内存中生成一个代表这个类的java.lang.Class对象,这个对象存放在方法区中。这个对象将作为程序访问方法区中的这些数据的外部接…

数学建模学习(5):数学建模各类题型及解题方案

一、数学建模常见的题型 总体来说,数学建模赛题类型主要分为:评价类、预测类和优化类三种,其中优化类是最常见的赛题类 型,几乎每年的地区赛或国赛美赛等均有出题,必须要掌握并且熟悉。 二、评价类赛题 综合评价是数学…

【线程安全的讨论(一)】CPU多核缓存架构和JMM

CPU多核缓存架构 一、CPU多核缓存架构可见性问题乱序执行(指令重排) 二、JMM——Java内存模型 一、CPU多核缓存架构 计算机的基本组成图 CPU 缓存为了提高程序运行的性能,现代 CPU 在很多方面会对程序进行优化。CPU 的处理速度很快&#xf…

DAY4,Qt(事件处理机制的使用,Qt中实现服务器的原理)

1.Qt中实现服务器的原理&#xff1b; ---chatser.h---头文件 #ifndef CHATSER_H #define CHATSER_H#include <QWidget> #include <QTcpServer> //服务器类 #include <QTcpSocket> //套接字类 #include <QMessageBox> //消息对话类 #include <Q…

【Golang】Golang进阶系列教程--Go 语言数组和切片的区别

文章目录 前言数组声明以及初始化函数参数 切片声明以及初始化函数参数 总结 前言 在 Go 语言中&#xff0c;数组和切片看起来很像&#xff0c;但其实它们又有很多的不同之处&#xff0c;这篇文章就来说说它们到底有哪些不同。 数组和切片是两个常用的数据结构。它们都可以用…

Sentinel 容灾中心的使用

Sentinel 容灾中心的使用 往期文章 Nacos环境搭建Nacos注册中心的使用Nacos配置中心的使用 熔断/限流结果 Jar 生产者 spring-cloud-alibaba&#xff1a;2021.0.4.0 spring-boot&#xff1a;2.6.8 spring-cloud-loadbalancer&#xff1a;3.1.3 sentinel&#xff1a;2021.0…

Jquery笔记

DOM对象通过jquery获取 所有的代码都是基于引入jquery.js文件 var mydiv $(#div);//直接获取到DOM对象元素id var mydiv$(.div)&#xff1b;//通过class获取DOM对象&#xff0c;如果有同名class只会获取第一个 var mysapn$(span);//通过元素的标签名获取DOM对象 var divarr$(…