当前位置: 首页 > news >正文

C# 设计模式之适配器模式

总目录


前言

在实际的开发过程中,由于需求的变化和扩展,我们的代码也需要做相应的扩展。想象这样一个场景,原项目中接口返回的数据是XML格式的数据,但现在来了一个新客户,它期望接口返回的数据类型为json格式的。想要实现要么就是改原有接口,但这样就违反了开闭原则,容易出现未知bug,影响到老客户的正常使用。而如果写一个适配器类也就是转换类(第三方类),将原本返回的XML格式数据转换成json格式数据,而具体数据是怎么来的则直接用原有接口方法就可以,这就是适配器模式。


1 基本介绍

  1. 定义:将一个类的接口转换成客户端所期望的另一个接口,使得原本由于接口不兼容而无法协同工作的类能够一起工作。
  2. 适配器模式有类的适配器模式和对象的适配器模式两种形式
  3. 适配器模式(Apapter Pattern)是一种结构型设计模式,用来解决现有对象与客户端期待接口不一致的问题
  4. 适配器模式中的角色:
    • 目标角色(Target):描述了其他类与客户端代码合作时必须遵循的协议。
    • 客户角色(Client):与符合Target接口的对象协同。
    • 被适配(服务类,功能类)(Adaptee):定义一个已经存在并已经使用的接口,这个接口需要适配。 客户端与其接口不兼容, 因此无法直接调用其功能。
    • 适配器(Adapter) :适配器模式的核心。适配器接受客户端通过适配器接口发起的调用,同时根据其内在逻辑调用对应服务类。客户端代码只需通过接口与适配器交互即可, 无需与具体的服务类耦合。

现实生活中空调插头一般都是三头的,但如果家里只有两孔插座,那必然是插不进去的。而如果提供一个拥有三孔插座和两头插头的转换器的话,那空调可以先插在这个转换器上,然后这个转换器再插在插座上就可以了。本质并没有变,只是将二孔插座包装了一下,向外界提供了一个三孔插座的外观以供客户使用。


适配的本质就是转换,将不满足使用条件的东西通过第三方类进行加工处理成可使用的东西。

2 使用场景

  • 系统需要复用现有类,但是接口又与复用环境要求不一致的情况。
  • 旧系统与新系统的兼容:可以使新系统能够无缝地与老旧系统进行通信。
  • 第三方组件的集成:适配器可以将第三方组件的接口转换为符合我们系统需求的接口形式,从而能够顺利地集成到我们的系统中。
  • 多个类库之间的互操作:适配器模式可以起到桥梁的作用。

3 实现方式

1. 类适配器

以常见的数据库辅助接口为例

    //数据库辅助接口【目标角色】public interface IDbHelper{//负责执行数据查询 Queryvoid ExecDQL(string sql);//负责执行数据操作 Create,Delete,Updatevoid ExecDML(string sql);}// SqlServer的辅助类 实现public class SqlServerHelper : IDbHelper{public void ExecDML(string sql){Console.WriteLine($"SqlServerHelper执行了【操作】sql:{sql}");}public void ExecDQL(string sql){Console.WriteLine($"SqlServerHelper执行了【查询】sql:{sql}");}}// SqlServer的辅助类 实现public class MySqlHelper : IDbHelper{public void ExecDML(string sql){Console.WriteLine($"MySqlHelper执行了【操作】sql:{sql}");}public void ExecDQL(string sql){Console.WriteLine($"MySqlHelper执行了【查询】sql:{sql}");}}//业务扩展了,关系型数据库已经不满足于现在的业务了//需要给系统增加缓存,用到了非关系型数据库//非关系数据库辅助类 【被适配者】public class NoSqlHelper{//非关系数据库【特有的】操作数据方法public void ExecNoSqlDML(string str){Console.WriteLine($"NoSqlHelper执行了【操作】:{str}");}//非关系数据库【特有的】查询数据方法public void ExecNoSqlDQL(string str){Console.WriteLine($"NoSqlHelper执行了【查询】:{str}");}}//适配器类public class NoSqlHelperAdapter : NoSqlHelper, IDbHelper{//实际上调用非关系型数据库的数据操作方法public void ExecDML(string sql){base.ExecNoSqlDML(sql);}public void ExecDQL(string sql){base.ExecNoSqlDQL(sql);}}

客户端调用

        public static void Main(string[] args){//客户端可以通过适配器来使用IDbHelper这个数据库辅助类//因为通过NoSqlHelperAdapter 适配器类,已经将其包装成了IDbHelperIDbHelper dbHelper = new NoSqlHelperAdapter();dbHelper.ExecDML("...");Console.ReadLine();}

从实例中可以看出,类适配器主要是用继承来实现的,但如果有很多个类进行适配,这个方式就不支持了。

2. 对象适配器

相对于类适配器,这部分代码不变

//数据库辅助类public interface IDbHelper{//负责执行数据查询 Queryvoid ExecDQL(string sql);//负责执行数据操作 Create,Delete,Updatevoid ExecDML(string sql);}// SqlServer的辅助类 实现public class SqlServerHelper : IDbHelper{public void ExecDML(string sql){Console.WriteLine($"SqlServerHelper执行了【操作】sql:{sql}");}public void ExecDQL(string sql){Console.WriteLine($"SqlServerHelper执行了【查询】sql:{sql}");}}// SqlServer的辅助类 实现public class MySqlHelper : IDbHelper{public void ExecDML(string sql){Console.WriteLine($"MySqlHelper执行了【操作】sql:{sql}");}public void ExecDQL(string sql){Console.WriteLine($"MySqlHelper执行了【查询】sql:{sql}");}}//业务扩展了,关系型数据库已经不满足于现在的业务了//需要给系统增加缓存,用到了非关系型数据库//非关系数据库辅助类public class NoSqlHelper{//非关系数据库操作数据public void ExecNoSqlDML(string str){Console.WriteLine($"NoSqlHelper执行了【操作】:{str}");}//非关系数据库查询数据public void ExecNoSqlDQL(string str){Console.WriteLine($"NoSqlHelper执行了【查询】:{str}");}}

仅仅是适配类的代码发生改变

    //适配器public class NoSqlHelperAdapter : IDbHelper{//引用非关系数据库辅助类 的实例public NoSqlHelper noSqlHelper = new NoSqlHelper();public void ExecDML(string sql){//通过实例调用相关数据操作的方法noSqlHelper.ExecNoSqlDML(sql);}public void ExecDQL(string sql){noSqlHelper.ExecNoSqlDQL(sql);}}

从实例中可以看出,对象适配器其实就是在适配器类中创建了一个被适配者的实例,从而将两者联系在一起。这种方式采用 “对象组合”的方式,更符合松耦合。

从两个案例上知道,适配器模式并不是项目一开始就会用到的,而是随着需求的变更和扩展,我们不得已才开发一个适配器类,将新增的功能通过适配器类 “包一层” 的方式转换为我们原有对外提供的接口,使得我们可以在不修改原有代码的基础上来复用现有类,很好地符合 “开闭原则”

4 优缺点分析

类的适配器模式:

  • 优点:
    • 可以在不修改原有代码的基础上来复用现有类,很好地符合 “开闭原则”
    • 可以重新定义Adaptee(被适配的类)的部分行为,因为在类适配器模式中,Adapter是Adaptee的子类
    • 仅仅引入一个对象,并不需要额外的字段来引用Adaptee实例(这个即是优点也是缺点)。
  • 缺点:
    • 采用了 “多继承”的实现方式,带来了不良的高耦合。

对象的适配器模式:

  • 优点:
    • 可以在不修改原有代码的基础上来复用现有类,很好地符合 “开闭原则”(这点是两种实现方式都具有的)
    • 采用 “对象组合”的方式,更符合松耦合。
  • 缺点:
    • 使得重定义Adaptee(被适配的类)的行为较困难,这就需要生成Adaptee的子类并且使得Adapter引用这个子类而不是引用Adaptee本身。

结语

希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。


参考资料:
c#中适配器模式详解
C#设计模式(7)-适配器模式

相关文章:

C# 设计模式之适配器模式

总目录 前言 在实际的开发过程中,由于需求的变化和扩展,我们的代码也需要做相应的扩展。想象这样一个场景,原项目中接口返回的数据是XML格式的数据,但现在来了一个新客户,它期望接口返回的数据类型为json格式的。想要…...

BFS实现迷宫最短路径

结合队列的知识利用 广度优先遍历,通过对能走的路径的记录以及对走过路径的标记,进行多条路搜查 一、理论基础 如下图的迷宫: 选取所走方向(针对某一个位置)下,右,上,左&#xff0…...

Linux IPC解析:匿名命名管道与共享内存

目录 一.IPC机制介绍二.匿名与命名管道1.匿名管道2.命名管道3.日志 三.共享内存三.System V 标准1.System V简介2.IPC在内核的数据结构设计3.信号量 一.IPC机制介绍 IPC(Inter-Process Communication,进程间通信)是计算机系统中不同进程之间交…...

Codeforces Round 964 (Div. 4) A~G

封面原图 画师ideolo A - AB Again? 题意 给你一个两位数&#xff0c;把他的个位和十位加起来 代码 #include <bits/stdc.h> using namespace std; typedef long long ll; typedef double db; typedef pair<int,int> pii; typedef pair<ll,ll> pll;voi…...

单体应用提高性能和处理高并发-使用缓存

要在单体应用中实现高并发&#xff0c;并利用缓存技术来提高性能&#xff0c;需要深入了解缓存的应用场景、选择合适的缓存工具&#xff0c;以及在具体代码中实现缓存策略。以下是详细说明如何在单体应用中使用缓存来处理高并发的内容&#xff0c;包括常见的缓存框架和实际的代…...

ollama教程——使用LangChain调用Ollama接口实现ReAct

ollama入门系列教程简介与目录 相关文章: Ollama教程——入门:开启本地大型语言模型开发之旅Ollama教程——模型:如何将模型高效导入到Ollama框架Ollama教程——兼容OpenAI API:高效利用兼容OpenAI的API进行AI项目开发Ollama教程——使用LangChain:Ollama与LangChain的强强…...

【Bug分析】Keil报错:error: #18:expected a “)“问题解决

【Bug分析】Keil报错&#xff1a;error: #18:expected a “&#xff09;”问题解决 前言bug查找bug解决方法小结 前言 keil编译时出现一个问题&#xff0c;缺少一个右括号。然后仔细查看代码&#xff0c;并没有括号缺失。 如下&#xff0c;代码括号正常。 bug查找 站内文章…...

MAC上设置快捷打开终端以及如何运用剪切快捷键

在Mac上设置一个快捷键&#xff0c;在当前文件夹中打开终端&#xff0c;你可以使用Automator创建一个服务&#xff0c;然后将其分配给一个快捷键。以下是步骤&#xff1a; 1. 创建Automator服务 打开 Automator&#xff08;你可以在应用程序文件夹中找到它&#xff0c;或使用…...

linux docker安装 gitlab后忘记root密码如何找回

1. docker ps - a 查看当前gitlab 当前的id2. docker exec -it gitlab /bin/bash 进入docker git 容器中【gitlab 注意可以上图中的name&#xff0c;也可以是id都可以的】,如下图3.gitlab-rails console -e production 输入该指令&#xff0c;启动Ruby on Rails控制台&…...

C语言典型例题27

《C程序设计教程&#xff08;第四版&#xff09;——谭浩强》 习题2.4 用下面的scanf函数输入数据 使a3,b7,x8.5,y71.8,c1A,c2a。问在键盘上怎么输入 代码 //《C程序设计教程&#xff08;第四版&#xff09;——谭浩强》 //习题2.4 用下面的scanf函数输入数据&#xff0c;使…...

clion开发stm32f4系列(一)————移植rt-thread os系统

前言 本次使用的rt-thread的版本为5.0.2基于rt-thread sudio生成的源码进行拷贝和修改工程基于上次创建工程的项目进行修改。本次工程只是用了serial和pin组件,其他后面用到再进行添加 拷贝rt-thread源码库 通过CMakeLists来进行管理 顶级(rt-thread目录) cmake_minimum_req…...

计算机网络(网络层)

网络层概述 网络层是干什么的&#xff1f; 网络层的主要任务是实现不同异构网络互连&#xff0c;进而实现数据包在各网络之间的传输相比于数据链路层的以太网通信&#xff0c;网络层则是将一个个数据链路层连接的以太网通过路由器连接起来。从而实现不同数据链路层的互联。 这…...

Python3 第六十六课 -- CGI编程

目录 一. 什么是 CGI 二. 网页浏览 三. CGI 架构图 四. Web服务器支持及配置 五. 第一个CGI程序 5.1. HTTP 头部 5.2. CGI 环境变量 六. GET和POST方法 6.1. 使用GET方法传输数据 6.1.1. 简单的url实例&#xff1a;GET方法 6.1.2. 简单的表单实例&#xff1a;GET方法…...

【Unity23种设计模式】之状态模式

首先创建一个项目 打开项目后复制至3个场景 命名为 创建一个空物体 命名为GameLoop 创建一个脚本GameLoop.cs 编写代码如下 将代码挂载至空物体GameLoop 将三个场景拖拽至Scenes In Build 分析下状态模式的类图 我们创新类图中的代码 编写ISceneState.cs 编写三个状态子类继承构…...

二叉树刷题,bfs刷题

有些题目&#xff0c;你按照拍脑袋的方式去做&#xff0c;可能发现需要在递归代码中调用其他递归函数计算字数的信息。一般来说&#xff0c;出现这种情况时你可以考虑用后序遍历的思维方式来优化算法&#xff0c;利用后序遍历传递子树的信息&#xff0c;避免过高的时间复杂度。…...

为什么要用分布式锁

单应用中,如果要确保多线程修改同一个资源的安全性 加synchronized就可以了 但是性能不高 而mybatis-plus的乐观锁就可以很好的解决这类问题 但是这样的锁机制,只在单应用中有效 试想,在分布式下,有没有可能出现多个应用中的线程同时去修改同一个数据资源的并发问题 例如A …...

python游戏开发之五子棋游戏制作

五子棋是一种源自中国的传统棋类游戏&#xff0c;起源可以追溯到古代。它是一种两人对弈的游戏&#xff0c;使用棋盘和棋子进行。棋盘通常是一个 1515 的网格&#xff0c;棋子分为黑白两色&#xff0c;双方轮流在棋盘上落子。游戏的目标是通过在棋盘上落子&#xff0c;使自己的…...

文件上传绕过最新版安全狗

本文来源无问社区&#xff0c;更多实战内容&#xff0c;渗透思路可前往查看http://www.wwlib.cn/index.php/artread/artid/9960.html http分块传输绕过 http分块传输⼀直是⼀个很经典的绕过⽅式&#xff0c;只是在近⼏年分块传输⼀直被卡的很死&#xff0c;很多waf都开始加 …...

常用API_2:应用程序编程接口:ArrayList

文章目录 ArrayList常用方法 案例 &#xff1a;上菜 ArrayList 常用方法 来自黑马程序员学习视频 案例 &#xff1a;上菜 待完善...

【Linux操作系统】进程的基本概念(PCB对象)详解

目录 一、进程的基本概念二、进程的描述组织&#xff08;PCB对象&#xff09;1.PCB的基本概念2.为什么要有PCB对象&#xff08;操作系统对进程的组织管理&#xff09;3.PCB对象的内部属性&#xff08;tast_struct结构体&#xff09; 三、查看进程1.ps指令2.top指令3.通过 /proc…...

vscode里如何用git

打开vs终端执行如下&#xff1a; 1 初始化 Git 仓库&#xff08;如果尚未初始化&#xff09; git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...

Objective-C常用命名规范总结

【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名&#xff08;Class Name)2.协议名&#xff08;Protocol Name)3.方法名&#xff08;Method Name)4.属性名&#xff08;Property Name&#xff09;5.局部变量/实例变量&#xff08;Local / Instance Variables&…...

电脑插入多块移动硬盘后经常出现卡顿和蓝屏

当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时&#xff0c;可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案&#xff1a; 1. 检查电源供电问题 问题原因&#xff1a;多块移动硬盘同时运行可能导致USB接口供电不足&#x…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具

文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

第25节 Node.js 断言测试

Node.js的assert模块主要用于编写程序的单元测试时使用&#xff0c;通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试&#xff0c;通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...

【Go】3、Go语言进阶与依赖管理

前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课&#xff0c;做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程&#xff0c;它的核心机制是 Goroutine 协程、Channel 通道&#xff0c;并基于CSP&#xff08;Communicating Sequential Processes&#xff0…...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI

前一阵子在百度 AI 开发者大会上&#xff0c;看到基于小智 AI DIY 玩具的演示&#xff0c;感觉有点意思&#xff0c;想着自己也来试试。 如果只是想烧录现成的固件&#xff0c;乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外&#xff0c;还提供了基于网页版的 ESP LA…...

C++ 基础特性深度解析

目录 引言 一、命名空间&#xff08;namespace&#xff09; C 中的命名空间​ 与 C 语言的对比​ 二、缺省参数​ C 中的缺省参数​ 与 C 语言的对比​ 三、引用&#xff08;reference&#xff09;​ C 中的引用​ 与 C 语言的对比​ 四、inline&#xff08;内联函数…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)

骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术&#xff0c;它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton)&#xff1a;由层级结构的骨头组成&#xff0c;类似于人体骨骼蒙皮 (Mesh Skinning)&#xff1a;将模型网格顶点绑定到骨骼上&#xff0c;使骨骼移动…...

3403. 从盒子中找出字典序最大的字符串 I

3403. 从盒子中找出字典序最大的字符串 I 题目链接&#xff1a;3403. 从盒子中找出字典序最大的字符串 I 代码如下&#xff1a; class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...