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

news/2024/9/19 19:00:16 标签: 设计模式, 适配器模式, 前端

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

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

适配器模式(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(): String
    fun getAge(): Int
    fun getSign(): String?
}

//数据源A
object DataSourceA {
    //模拟返回数据
    fun getData(): IUser {
        return object: IUser {
            override fun getName(): String = "白泽"
            override fun getAge(): Int = 18
            override 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.niftyadmin.cn/n/5665996.html

相关文章

【machine learning-六-supervise learning之线性回归模型】

监督学习之线性回归模型 线性回归模型线性模型回归模型 如何使用线性模型实现智能化预测呢寻找数据训练模型输入、特征、目标、预测值、模型代价函数 线性模型是人工智能监督学习中最广泛的应用,所以有必要先学习一下这个基础模型,做好基石。 线性回归模…

ICPC网络预选赛1G题

The Median of the Median of the Median - Problem - QOJ.ac 给定一个序列{ai},i从1到n,首先构造一个序列{bij},i和j同样是从1到n,bij表示{ai....aj}的中位数,然后再构造一个{cij},cij表示{bii.....bij.....b(i1,i1).......b(i1,j).......bjj}的中位数,最后要求输出序列{cij}…

[数据集][目标检测]红外微小目标无人机直升机飞机飞鸟检测数据集VOC+YOLO格式7559张4类别

数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):7559 标注数量(xml文件个数):7559 标注数量(txt文件个数):7559 标注…

code eintegrity npm err sha512

当 npm install 出现报错的时候: 你应该这样去解决: 删除 package-lock.json 文件,重新执行 npm install。 问题出现的原因 EINTEGRITY 错误码表示在npm缓存中无法找到 指定sha512校验合的模块。 出现这个问题的原因是缓存不一致&…

YOLOv8改进系列,YOLOv8的Neck替换成AFPN(CVPR 2023)

摘要 多尺度特征在物体检测任务中对编码具有尺度变化的物体非常重要。多尺度特征提取的常见策略是采用经典的自上而下和自下而上的特征金字塔网络。然而,这些方法存在特征信息丢失或退化的问题,影响了非相邻层次的融合效果。一种渐进式特征金字塔网络(AFPN),以支持非相邻…

AI问答-HTTP:理解 Content-Disposition

本文背景 在下载arraybuffer文件时,想要获取文件名,这时引入本文内容Content-Disposition,我们在Content-Disposition获取到文件名就可以在下载后的文件以该文件名命名了。 一、简介 Content-Disposition是HTTP协议中的一个响应头字段&…

若依Nodejs后台、实现90%以上接口,附体验地址、源码、拓展特色功能

背景 前端的宝子们代码写累了吗?那就一起研究下后端吧! 体验地址:http://106.54.233.63:5000 Gitee源码:https://gitee.com/ruirui-study/ruoyi_nodejs_open 本项目的前端基于若依Vue3.0版本,后端是基于MidwayJs框…

inBuilder低代码平台新特性推荐-第二十四期

今天给大家带来的是 inBuilder 低代码平台新特性推荐第二十四期 ——表单格式支持流程配置。 场景介绍: 如下图所示,目前支持在流程设计上的不同节点设置表单字段的必填、显隐等属性控制,不必在表单设计上进行配置,从而减少了开…