重修设计模式-结构型-适配器模式

server/2024/9/24 9:21:26/

重修设计模式-结构型-适配器模式

将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作

适配器模式(Adapter Pattern)允许将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。简单来说,适配器解决的就是接口不兼容问题,引入适配器充当中间桥梁,从而让不兼容接口协同工作。

适配器模式的三个角色:

  1. 目标接口(Target):客户端所期望的接口,可以是抽象类、接口或具体的类。
  2. 需要适配的类(Adaptee):需要适配的类或接口,它通常是一个已经存在的、与客户端期望的接口不兼容的类。
  3. 适配器(Adapter):适配器类的核心,负责将 Adaptee 的接口转换成 Target 的接口。适配器类通常是具体类,通过继承或组合的方式与 Adaptee 发生关联。

适配器模式的两种实现:

  • 类适配器:使用继承来实现适配。

    通过继承将一个接口与另一个接口进行匹配,比较适合 AdapteeTarget 定义相似的场景,这种场景使用继承可以让代码少一些,代码可读性会高一些。

  • 对象适配器:使用组合来实现适配。

    通过组合的方式,使用适配器类需要适配的类(Adaptee)的实例包装起来,适配器类通过调用 Adaptee 的实例方法来实现目标接口。

举个例子,程序中存在这样的逻辑,从数据源A(DataSourceA) 处获取用户数据,并调用 saveUser 进行用户数据的处理,saveUser 只接收 IUser 接口类型的数据。这时需求变更,需要扩展出另一个数据源B(DataSourceB),但该数据源返回的数据类型为 UserB,saveUser 并不能接收 UserB 类型数据,代码表示如下:

//Target
interface IUser {fun getName(): Stringfun getAge(): Intfun getSign(): String?
}//数据源A
object DataSourceA {//模拟返回数据fun getData(): IUser {return object: IUser {override fun getName(): String = "白泽"override fun getAge(): Int = 18override fun getSign(): String? = "人生得意须尽欢"}}
}//Adaptee
class UserB(val nick: String, val birthday: String)//新增的数据源B
object DataSourceB {//模拟返回数据fun getData(): UserB {return UserB("白泽", "1996")}
}//处理User数据
fun saveUser(user: IUser) {//处理user相关逻辑println("处理用户信息 name:${user.getName()} age:${user.getAge()} sign:${user.getSign()}")
}//调用处:
fun main() {val user1 = DataSourceA.getData()saveUser(user1) //正常调用val user2 = DataSourceB.getData()//saveUser(user2) //类型不兼容
}

1.使用继承类方式实现适配器,创建适配器(ExtendUser),让其继承需要适配的类(UserB),并实现目标接口(IUser ),并覆写相关逻辑:

//适配器-继承
class ExtendUser(nick: String, birthday: String): UserB(nick, birthday), IUser {override fun getName(): String {return super.nick}override fun getAge(): Int {return Date().year - (super.birthday.toIntOrNull() ?: 0)}override fun getSign(): String? {return null}
}object DataSourceB {...//新增返回ExtendUser类型方法fun getDataExtend(): ExtendUser {return ExtendUser("白泽", "1996")}
}//调用处:
fun main() {val user2 = DataSourceB.getDataExtend()saveUser(user2)
}

其实更优方式是让 UserB 直接实现 IUser 接口,这里为了明确三个适配器角色还是额外抽出一个适配器类,但切记设计模式不能死记硬背,因地制宜才是代码设计的核心。

2.通过组合方式实现适配器,创建适配器(AdapterUser),其内持有需要适配的类的实例(UserB),并实现目标接口(IUser):

//适配器-组合
class AdapterUser(val user: UserB): IUser {override fun getName(): String {return user.nick}override fun getAge(): Int {return Date().year - (user.birthday.toIntOrNull() ?: 0)}override fun getSign(): String? {return null}
}fun main() {val user2 = DataSourceB.getData()val adapterUser = AdapterUser(user2)saveUser(adapterUser)
}

这两种方式都可以实现适配器模式,区别在于以下几点:

  1. 继承方式会有语言层单继承的限制,如果 Adaptee 和 Target 都不是接口,继承方式就无能为力了。
  2. 继承适合 Adaptee 和 Target 接口定义大部分相同场景,用代码语义相似性来让可读性更高;组合侧重部分可以在整体内自由变化,适合更加灵活的场景。

代理、桥接、装饰器、适配器 4 种设计模式的区别:

代理、桥接、装饰器、适配器,这 4 种模式的代码结构非常相似。笼统来说,它们都可以称为 Wrapper 模式,也就是通过 Wrapper 类二次封装原始类。

尽管代码结构相似,但这 4 种设计模式的用意完全不同,也就是说要解决的问题、应用场景不同,这也是它们的主要区别。这里我就简单说一下它们之间的区别。

代理模式:代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,独立思考非相关功能,这是它跟装饰器模式最大的不同。

桥接模式:桥接模式的目的是将抽象部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。

装饰器模式:装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。

适配器模式适配器模式既可以视作补救策略,也可以视作统一规范。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口。

适配器模式使用场景

一方面适配器模式可以看作一种“补偿模式”,用来补救设计上的缺陷,协调规避接口不兼容的问题。另一方面,适配器也可以在设计之初特意为变化预留接口,定制适配规范。

比如 Android 中列表控件 RecyclerView 就是适配器模式的经典运用,RecyclerView.Adapter 是抽象出来的适配规范,RecyclerView 内部只需要关注 View 的缓存,绘制和展示即可,而无需关注具体 View 是哪个,如何绑定数据等操作,这些逻辑都由调用方自己实现适配器并处理。 JVM 虚拟机也可以用适配器的思想去看,JVM 就相当于一个适配器,它向下屏蔽了各种系统的差异,任何语言只需要遵循 JVM 的相关规范,都可以运行在 JVM 虚拟机上,比如 Java、Kotlin、Groovy、JRuby、Jython、Scala等语言都是基于 JVM 规范而开发出来的。


http://www.ppmy.cn/server/121277.html

相关文章

中秋节特别游戏:给玉兔投喂月饼

🖼️ 效果展示 📜 游戏背景 在中秋这个充满诗意的节日里,玉兔因为贪玩被赶下人间。在这个温柔的夜晚,我们希望通过一个小游戏,让玉兔感受到人间的温暖和关怀。🐰🌙 🎮 游戏设计 人…

后端开发刷题 | 没有重复项数字的全排列

描述 给出一组数字,返回该组数字的所有排列 例如: [1,2,3]的所有排列如下 [1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2], [3,2,1]. (以数字在数组中的位置靠前为优先级,按字典序排列输出。) 数据范围:数字…

【小米手机无法连接电脑】一般问题和驱动MTP问题的结局ue

一般 那一般就需要换个电脑。看一看自己的手机是不是能连得上。如果连得上,就拿刚才的那根线连到你的电脑上,并且换一个电脑的接口,再试试。 如果连不上,换一下原装的数据线再试试。 如果还是不行,需要把自己的手机打…

直接在tomcat下面访问jsp

复制一份tomcat为tomcat-8.5.99test 记住修改tomcat-8.5.99test下面bin/startup.sh(Linux/Mac)或 bin/startup.bat(Windows) 在 Linux/Mac export CATALINA_BASE/path/to/tomcat1 $CATALINA_HOME/bin/startup.sh 在 Windows: …

【通信基础】精讲通信天线种类及CAN总线和集群关系

前言 在通信行业中,天线的种类非常多,涵盖了从简单的无线电天线到复杂的相控阵天线。这些天线的种类和形态各异,以满足不同频率、应用场景和通信需求。以下是一些主要的天线种类: 1. 通信天线种类 1.1 偶极子天线 (Dipole Ant…

鹏哥C语言42---函数调用相关练习

#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> //------------------------------------打印1000-2000年之间的闰年--------------------------------------------------- //闰年的判断规则有两个 //1.能被4整除&#xff0c;但是不能被100整除 //2.能被400整除也是…

ElasticSearch分页查询性能及封装实现

Es的分页方式 fromsize 最基本的分页方式&#xff0c;类似于SQL中的Limit语法&#xff1a; //查询年龄在12到32之间的前15条数据 {"query":{"bool":{"must":{"range":{"user_age":{"gte":12,"lte":3…

C语言——自定义类型

目录 结构体 概念 结构体变量的创建和初始化 结构体的自引用 结构体的内存对齐 内存对齐存在的原因 合理设计结构体 方法一 方法二 结构体传参 结构体实现位段 什么是位段 位段的内存分配 位段的跨平台问题 注意 联合体 概念 验证 优点 小应用 什么是大小…