适配器模式:让不兼容接口协同工作

news2025/6/6 12:22:47

文章目录

    • 1. 适配器模式概述
    • 2. 适配器模式的分类
      • 2.1 类适配器
      • 2.2 对象适配器
    • 3. 适配器模式的结构
    • 4. C#实现适配器模式
      • 4.1 对象适配器实现
      • 4.2 类适配器实现
    • 5. 适配器模式的实际应用场景
      • 5.1 第三方库集成
      • 5.2 遗留系统集成
      • 5.3 系统重构与升级
      • 5.4 跨平台开发
    • 6. 类适配器与对象适配器的对比
      • 6.1 类适配器
      • 6.2 对象适配器
    • 7. 适配器模式与其他模式的关系
      • 7.1 适配器模式 vs 桥接模式
      • 7.2 适配器模式 vs 装饰器模式
      • 7.3 适配器模式 vs 外观模式
    • 8. 适配器模式的优缺点
      • 8.1 优点
      • 8.2 缺点
    • 9. .NET中的适配器模式应用
      • 9.1 数据访问适配器
      • 9.2 IO流适配器
    • 10. 总结

1. 适配器模式概述

适配器模式(Adapter Pattern)是一种结构型设计模式,它的主要目的是将一个类的接口转换成客户端所期望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。简言之,适配器模式就是提供了一个中间层,让原本接口不兼容的类可以协同工作。

在日常生活中,适配器的例子比比皆是:

  • 电源适配器:将不同国家的电源插座标准转换为设备所需的电压和插座类型
  • 读卡器:将存储卡中的数据适配到电脑可识别的接口
  • 转接头:比如Type-C转HDMI接口的转接头

在软件开发中,当我们需要使用一个已存在的类,但是其接口与我们需要的不一致时,我们就可以使用适配器模式。

2. 适配器模式的分类

根据实现方式的不同,适配器模式可以分为两种主要类型:

2.1 类适配器

类适配器使用多重继承(在C#中是通过继承一个类并实现接口的方式)来达到适配的目的。

2.2 对象适配器

对象适配器使用组合关系,将被适配的对象作为适配器的一个成员变量。这种方式更加灵活,符合"组合优于继承"的设计原则。

3. 适配器模式的结构

依赖
实现
关联
Client
method()
«interface»
Target
request()
Adapter
request()
Adaptee
specificRequest()

适配器模式主要包含以下几个核心角色:

  1. 目标(Target):定义客户端使用的接口。

  2. 适配者(Adaptee):已存在的、具有特殊功能但接口不符合目标接口的类。

  3. 适配器(Adapter):实现目标接口并包含对适配者实例的引用,在目标接口的方法中调用适配者的相应方法。

  4. 客户端(Client):与目标接口交互的类。

以下是更详细的类图,展示了类适配器和对象适配器的区别:

依赖
实现
组合
继承
实现
Client
Main()
«interface»
ITarget
Request()
Adaptee
SpecificRequest()
ObjectAdapter
adaptee: Adaptee
Request()
ClassAdapter
Request()
SpecificRequest()

适配器模式的工作流程可以通过以下序列图来说明:

Client Adapter Adaptee 请求服务(Request) 转换并调用特定方法(SpecificRequest) 返回结果 转换并返回结果 Client Adapter Adaptee

从序列图中可以看出,客户端并不直接与适配者交互,而是通过适配器进行间接交互。适配器负责将客户端的请求转换为适配者能够理解的形式,并将适配者的响应转换回客户端期望的形式。

4. C#实现适配器模式

下面我们通过一个现实案例来演示适配器模式的实现。假设我们有一个第三方的媒体播放库,但它的接口与我们的应用程序所需的接口不兼容,我们需要创建一个适配器来解决这个问题。

4.1 对象适配器实现

首先,我们定义客户端期望的目标接口:

/// <summary>
/// 目标接口:定义客户端使用的接口
/// </summary>
public interface IMediaPlayer
{
    void Play(string audioType, string fileName);
}

然后,我们有一个已经存在的适配者类(假设是第三方库):

/// <summary>
/// 适配者类:已存在的视频播放器
/// </summary>
public class AdvancedMediaPlayer
{
    public void PlayVlc(string fileName)
    {
        Console.WriteLine($"播放VLC文件:{fileName}");
    }

    public void PlayMp4(string fileName)
    {
        Console.WriteLine($"播放MP4文件:{fileName}");
    }
}

现在,我们创建适配器类,将AdvancedMediaPlayer适配到IMediaPlayer接口:

/// <summary>
/// 适配器类:将AdvancedMediaPlayer适配到IMediaPlayer
/// </summary>
public class MediaAdapter : IMediaPlayer
{
    private AdvancedMediaPlayer _advancedMediaPlayer;

    public MediaAdapter(string audioType)
    {
        _advancedMediaPlayer = new AdvancedMediaPlayer();
    }

    public void Play(string audioType, string fileName)
    {
        if (audioType.Equals("vlc", StringComparison.OrdinalIgnoreCase))
        {
            _advancedMediaPlayer.PlayVlc(fileName);
        }
        else if (audioType.Equals("mp4", StringComparison.OrdinalIgnoreCase))
        {
            _advancedMediaPlayer.PlayMp4(fileName);
        }
        else
        {
            Console.WriteLine($"不支持的媒体类型:{audioType}");
        }
    }
}

最后,创建客户端类:

/// <summary>
/// 客户端类:使用IMediaPlayer接口播放不同类型的媒体文件
/// </summary>
public class AudioPlayer : IMediaPlayer
{
    public void Play(string audioType, string fileName)
    {
        // 内置支持播放mp3文件
        if (audioType.Equals("mp3", StringComparison.OrdinalIgnoreCase))
        {
            Console.WriteLine($"播放MP3文件:{fileName}");
        }
        // 通过适配器支持播放其他格式
        else if (audioType.Equals("vlc", StringComparison.OrdinalIgnoreCase) ||
                 audioType.Equals("mp4", StringComparison.OrdinalIgnoreCase))
        {
            MediaAdapter mediaAdapter = new MediaAdapter(audioType);
            mediaAdapter.Play(audioType, fileName);
        }
        else
        {
            Console.WriteLine($"不支持的媒体类型:{audioType}");
        }
    }
}

使用示例:

static void Main(string[] args)
{
    AudioPlayer audioPlayer = new AudioPlayer();
    
    // 播放mp3文件(内置支持)
    audioPlayer.Play("mp3", "beyond the horizon.mp3");
    
    // 通过适配器播放vlc文件
    audioPlayer.Play("vlc", "far far away.vlc");
    
    // 通过适配器播放mp4文件
    audioPlayer.Play("mp4", "alone.mp4");
    
    // 尝试播放不支持的格式
    audioPlayer.Play("avi", "mind me.avi");
    
    Console.ReadLine();
}

输出结果:

播放MP3文件:beyond the horizon.mp3
播放VLC文件:far far away.vlc
播放MP4文件:alone.mp4
不支持的媒体类型:avi

4.2 类适配器实现

在C#中,由于不支持多重继承,类适配器通常通过继承适配者类并实现目标接口来实现。下面是一个简单的例子:

/// <summary>
/// 类适配器:通过继承AdvancedMediaPlayer并实现IMediaPlayer接口
/// </summary>
public class MediaClassAdapter : AdvancedMediaPlayer, IMediaPlayer
{
    public void Play(string audioType, string fileName)
    {
        if (audioType.Equals("vlc", StringComparison.OrdinalIgnoreCase))
        {
            PlayVlc(fileName);
        }
        else if (audioType.Equals("mp4", StringComparison.OrdinalIgnoreCase))
        {
            PlayMp4(fileName);
        }
        else
        {
            Console.WriteLine($"不支持的媒体类型:{audioType}");
        }
    }
}

5. 适配器模式的实际应用场景

适配器模式在实际软件开发中有广泛的应用:

5.1 第三方库集成

当需要集成第三方库,但其API与系统现有接口不兼容时,可以使用适配器模式创建一个中间层。

5.2 遗留系统集成

在企业应用中,经常需要将新系统与遗留系统集成,适配器模式可以帮助解决接口不兼容的问题。

5.3 系统重构与升级

在系统升级过程中,为了保持对现有代码的兼容,可以通过适配器模式提供向后兼容性。

5.4 跨平台开发

在跨平台开发中,不同平台可能有不同的API,适配器模式可以提供一个统一的接口。

6. 类适配器与对象适配器的对比

适配器模式
类适配器
对象适配器
通过多重继承/接口实现
通过组合/聚合关系实现
优点: 不需要额外对象
缺点: 耦合度高
优点: 灵活性高
缺点: 需要额外对象

6.1 类适配器

优点:

  • 适配器可以重写适配者的方法,提供更加灵活的适配
  • 不需要创建额外的对象,减少了内存开销

缺点:

  • 使用了继承,导致高耦合
  • 在C#等只支持单继承的语言中,一旦适配器类继承了适配者类,就不能再继承其他类

6.2 对象适配器

优点:

  • 使用组合代替继承,遵循"组合优于继承"的原则
  • 可以适配多个适配者类
  • 低耦合,适配者类的修改不会直接影响到适配器

缺点:

  • 需要创建额外的对象
  • 不能覆盖适配者类的行为

7. 适配器模式与其他模式的关系

7.1 适配器模式 vs 桥接模式

  • 适配器模式是事后补救,用于解决已有接口不兼容的问题
  • 桥接模式是事前预防,用于将抽象与实现分离,使它们可以独立变化

7.2 适配器模式 vs 装饰器模式

  • 适配器模式改变接口以匹配客户端的期望
  • 装饰器模式保持接口不变,但增加了对象的职责

7.3 适配器模式 vs 外观模式

  • 适配器模式使得两个现有的接口能够协同工作
  • 外观模式定义一个更高级的接口,简化了子系统的使用

8. 适配器模式的优缺点

8.1 优点

  1. 增加类的透明性:通过适配器,客户端可以调用同一接口,无需知道被适配者的存在。

  2. 提高类的复用性:将现有的类包装成目标接口,复用现有的功能。

  3. 灵活性和扩展性好:可以在不修改现有代码的情况下增加新的适配器,满足新的需求。

  4. 遵循开闭原则:可以引入新的适配器而无需修改现有代码,适配器本身也可以在不修改客户端代码的情况下替换。

8.2 缺点

  1. 增加系统的复杂性:引入适配器会增加系统的复杂性,因此只有在必要时才应使用。

  2. 可能需要更多的代码:需要编写额外的适配器类。

  3. 可能存在性能损失:适配器中的额外间接调用可能会导致一些性能损失。

9. .NET中的适配器模式应用

在.NET框架中,适配器模式有许多实际应用:

9.1 数据访问适配器

ADO.NET中的DataAdapter类就是一个典型的适配器模式应用。它将不同数据库提供者的特定操作适配到统一的接口,使得应用程序可以用一致的方式操作不同的数据库。

// 创建SqlDataAdapter(适配SQL Server数据库)
SqlDataAdapter sqlAdapter = new SqlDataAdapter("SELECT * FROM Customers", connectionString);

// 创建OracleDataAdapter(适配Oracle数据库)
OracleDataAdapter oracleAdapter = new OracleDataAdapter("SELECT * FROM Customers", oracleConnectionString);

// 客户端代码可以以相同的方式使用不同的适配器
DataSet dataSet = new DataSet();
sqlAdapter.Fill(dataSet);  // 或 oracleAdapter.Fill(dataSet);

9.2 IO流适配器

.NET中的流适配器如StreamReaderStreamWriter也应用了适配器模式。它们将字节流转换为字符流,使得开发者可以更方便地处理文本数据。

// 创建一个文件流
FileStream fileStream = new FileStream("file.txt", FileMode.Open);

// 使用StreamReader适配器将字节流转换为字符流
StreamReader reader = new StreamReader(fileStream);

// 现在可以按行读取文本了
string line;
while ((line = reader.ReadLine()) != null)
{
    Console.WriteLine(line);
}

10. 总结

适配器模式是一种非常实用的设计模式,它解决了接口不兼容的问题,使得原本不能一起工作的类可以协同工作。适配器模式主要有两种实现方式:类适配器和对象适配器,每种方式都有其适用场景。

在实际开发中,适配器模式常用于以下场景:

  • 集成第三方库
  • 系统重构或升级
  • 构建与多个外部系统交互的应用程序
  • 为遗留系统提供新的接口

通过合理使用适配器模式,可以提高代码的复用性、灵活性和可维护性,使系统更容易适应变化的需求。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DDP与FSDP:分布式训练技术全解析

DDP与FSDP:分布式训练技术全解析 DDP(Distributed Data Parallel)和 FSDP(Fully Sharded Data Parallel)均为用于深度学习模型训练的分布式训练技术,二者借助多 GPU 或多节点来提升训练速度。 1. DDP(Distributed Data Parallel) 实现原理 数据并行:把相同的模型复…

【Spring AI 1.0.0】Spring AI 1.0.0框架快速入门(1)——Chat Client API

Spring AI框架快速入门 一、前言二、前期准备2.1 运行环境2.2 maven配置2.3 api-key申请 三、Chat Client API3.1 导入pom依赖3.2 配置application.properties文件3.3 创建 ChatClient3.3.1 使用自动配置的 ChatClient.Builder3.3.2 使用多个聊天模型 3.4 ChatClient请求3.5 Ch…

【笔记】在 MSYS2(MINGW64)中正确安装 Rust

#工作记录 1. 环境信息 Windows系统: MSYS2 MINGW64当前时间: 2025年6月1日Rust 版本: rustc 1.87.0 (17067e9ac 2025-05-09) (Rev2, Built by MSYS2 project) 2. 安装步骤 步骤 1: 更新系统包数据库并升级已安装的包 首先&#xff0c;确保我们的 MSYS2 系统是最新状态。打…

从汇编的角度揭秘C++引用,豁然开朗

C中的引用是指已有对象的别名&#xff0c;可以通过该别名访问并修改被引用的对象。那么其背后的原理是什么呢&#xff1f;引用是否会带来额外的开销呢&#xff1f;我们从一段代码入手&#xff0c;来分析一下引用的本质。 #include <stdio.h> int main() {int a 10;int …

聊聊Tomato Architecture

序 本文主要研究一下Tomato Architecture Clean/Onion/Hexagonal/Ports&Adapters Architectures Clean Architecture clean architecture定义了四层结构&#xff0c;最内层是entities(enterprise business rules)&#xff0c;再往外是use cases(application business ru…

小白的进阶之路系列之十二----人工智能从初步到精通pytorch综合运用的讲解第五部分

在本笔记本中,我们将针对Fashion-MNIST数据集训练LeNet-5的变体。Fashion-MNIST是一组描绘各种服装的图像瓦片,有十个类别标签表明所描绘的服装类型。 # PyTorch model and training necessities import torch import torch.nn as nn import torch.nn.functional as F impor…

2025年06月03日Github流行趋势

项目名称&#xff1a;onlook 项目地址url&#xff1a;https://github.com/onlook-dev/onlook项目语言&#xff1a;TypeScript历史star数&#xff1a;12871今日star数&#xff1a;624项目维护者&#xff1a;Kitenite, drfarrell, spartan-vutrannguyen, apps/devin-ai-integrati…

【数据分析】基于Cox模型的R语言实现生存分析与生物标志物风险评估

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍加载R包数据下载导入数据数据预处理生存分析画图输出图片其他标记物的分析总结系统信息介绍 分析生存数据与多种生物标志物之间的关系。它通过Cox比例风险模型来评估不同生物标志物…

使用nginx配置反向代理,负载均衡

首先啥叫反向代理 咋配置呢&#xff0c;那当然是在nginx目录下改conf文件了 具体咋改呢&#xff0c;那就新增一个新的server配置&#xff0c;然后在location里新增你想代理的服务器 实际上负载均衡也就是根据反向代理的思路来的&#xff0c;如下所示 配置的话实际上也与上…

从 iPhone 备份照片: 保存iPhone图片的5种方法

随着智能手机越来越融入我们的生活&#xff0c;我们的照片已成为我们设备上最有价值的数据形式之一。然而&#xff0c;iPhone内部存储空间仍然有限&#xff0c;因此我们需要将iPhone中的照片备份到另一个地方&#xff0c;以释放空间并确保珍贵的图像记忆的安全。阅读本指南&…

Spring Ai 从Demo到搭建套壳项目(一)初识与实现与deepseek对话模式

前言 为什么说Java长青&#xff0c;主要是因为其生态圈完善&#xff0c;Spring又做了一款脚手架&#xff0c;把对接各个LLM厂商的sdk做了一遍&#xff0c;形成一系列的spring-ai-starter-** 的依赖。 目前为止版本去到1.0.0.M6&#xff0c;golang跟不上了吧&#xff0c; Make …

快速上手pytest

1. pytest的基础 1.1 什么是pytest pytest 是 python 中的一个测试框架&#xff0c;用于编写简洁、可扩展的测试代码&#xff0c;帮助开发者验证结果是否与预期相符。 python 中有很多的测试框架&#xff0c;那我们为什么要学习 pytest 呢&#xff1f; pytest 的优势&…

设备驱动与文件系统:02 键盘

操作系统中键盘驱动的讲解 在这一讲中&#xff0c;我将为大家讲解键盘相关内容。从上一讲开始&#xff0c;我们进入了操作系统第四个部分的学习&#xff0c;也就是操作系统对设备的驱动与管理。 上一讲我们探讨的是显示器&#xff0c;并且提到&#xff0c;一个终端设备是由显示…

交通违法拍照数据集,可识别接打电话,不系安全带的行为,支持YOLO,COCO JSON,VOC XML格式的标注数据集 最高正确识别率可达88.6%

交通违法拍照数据集 数据集概述 数据来源&#xff1a;交通监控摄像头、执法记录仪、公开数据集数据类型&#xff1a;图像、视频、元数据&#xff08;时间、地点、车辆信息&#xff09;违法类型标注&#xff1a;接打电话、未系安全带 数据采集与标注方法 采集设备&#xff1…

Qt OpenGL 3D 编程入门

Qt 提供了强大的 OpenGL 集成功能&#xff0c;使得在 Qt 应用中实现 3D 图形变得更加简单。以下是使用 Qt 进行 OpenGL 3D 编程的基础知识。 1. 环境配置 创建 Qt 项目 新建 Qt Widgets Application 项目 在 .pro 文件中添加 OpenGL 模块&#xff1a; qmake QT co…

性能优化 - 工具篇:基准测试 JMH

文章目录 Pre引言1. JMH 简介2. JMH 执行流程详解3. 关键注解详解3.1 Warmup3.2 Measurement3.3 BenchmarkMode3.4 OutputTimeUnit3.5 Fork3.6 Threads3.7 Group 与 GroupThreads3.8 State3.9 Setup 与 TearDown3.10 Param3.11 CompilerControl 4. 示例代码与分析4.1 关键点解读…

Nginx网站服务:从入门到LNMP架构实战

&#x1f3e1;作者主页&#xff1a;点击&#xff01; Nginx-从零开始的服务器之旅专栏&#xff1a;点击&#xff01; &#x1f427;Linux高级管理防护和群集专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2025年5月30日14点22分 前言 说起Web服务器&#xff0c…

Java面试八股--08-数据结构和算法篇

1、怎么理解时间复杂度和空间复杂度 时间复杂度和空间复杂度一般是针对算法而言&#xff0c;是衡量一个算法是否高效的重要标准。先纠正一个误区&#xff0c;时间复杂度并不是算法执行的时间&#xff0c;在纠正一个误区&#xff0c;算法不单单指冒泡排序之类的&#xff0c;一个…

Java面试八股--06-Linux篇

目录 一、Git 1、工作中git开发使用流程&#xff08;命令版本描述&#xff09; 2.Reset与Rebase&#xff0c;Pull与Fetch的区别 3、git merge和git rebase的区别 4、git如何解决代码冲突 5、项目开发时git分支情况 二、Linux 1、Linux常用的命令 2、如何查看测试项目的…

dvwa7——SQL Injection

LOW&#xff1a; f12打开hackbar 一&#xff1a;判断注入类型 输入id1报错 闭合单引号 &#xff0c;页面恢复正常 所以为单引号字符型 二&#xff1a;开始攻击 1.判断列数 ?id1 order by 2-- 到3的时候开始报错&#xff0c;所以一共两列 2.爆回显位置 ?id-1 union s…