零基础设计模式——结构型模式 - 适配器模式

news2025/5/29 7:04:35

第三部分:结构型模式 - 适配器模式 (Adapter Pattern)

欢迎来到结构型模式的第一站!结构型模式关注的是如何将类或对象组合成更大的结构,同时保持结构的灵活性和效率。适配器模式是其中非常实用的一个,它能帮助我们解决接口不兼容的问题。

  • 核心思想:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

适配器模式 (Adapter Pattern)

“将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。”

想象一下,你去国外旅行,你带的电器插头是两孔的,但酒店的插座是三孔的。这时你就需要一个“电源适配器”,它的一端可以插入三孔插座,另一端提供两孔插口,让你的电器能够正常工作。这个电源适配器就是“适配器模式”的体现。

1. 目的 (Intent)

适配器模式的主要目的:

  1. 接口转换:核心目的是将一个已存在的类(被适配者 Adaptee)的接口转换成客户端所期望的另一个接口(目标接口 Target)。
  2. 复用现有代码:使得客户端可以复用一些功能强大但接口不兼容的已有类。
  3. 解耦:将客户端与被适配者解耦。客户端只需要与目标接口交互,而不需要知道被适配者的具体实现。

2. 生活中的例子 (Real-world Analogy)

  • 电源适配器:如上所述,不同国家/地区的电源插座和电器插头标准不同,电源适配器可以转换它们,使其兼容。
  • 读卡器:你的电脑可能只有USB接口,但你想读取SD卡或TF卡中的数据。读卡器就充当了适配器,将SD卡/TF卡的接口转换成USB接口,让电脑可以访问卡内数据。
  • 翻译官:两个人说不同的语言(比如中文和英文),无法直接交流。翻译官(适配器)可以将一方的语言(中文)转换成另一方能理解的语言(英文),反之亦然,从而实现沟通。
  • Java中的 Arrays.asList():这个方法可以将一个数组转换成一个 List 对象。数组和 List 的操作接口不同,asList() 创建了一个适配器,使得你可以像操作 List 一样操作底层数组(但要注意这个 List 是固定大小的)。
  • Java中的 InputStreamReaderOutputStreamWriter:它们是字节流和字符流之间的桥梁(适配器)。InputStreamReader 将字节输入流 InputStream 转换为字符输入流 ReaderOutputStreamWriter 将字符输出流 Writer 转换为字节输出流 OutputStream

3. 结构 (Structure)

适配器模式主要有两种实现形式:类适配器和对象适配器。

a. 类适配器模式 (Class Adapter)
  • 原理:通过类的继承来实现。适配器类继承了被适配者类,并同时实现了目标接口。
  • 角色
    1. Target (目标接口):客户端期望的接口。可以是抽象类或接口。
    2. Adaptee (被适配者):需要被适配的类,它拥有客户端需要的功能,但其接口与 Target 不兼容。
    3. Adapter (适配器):继承 Adaptee 类并实现 Target 接口。它通过调用继承来的 Adaptee 的方法来完成 Target 接口定义的功能。
    4. Client (客户端):通过 Target 接口与 Adapter 交互。
      在这里插入图片描述

缺点

  • 由于适配器继承了被适配者,所以它不能再继承其他类(在单继承语言如Java中)。
  • 适配器直接暴露了被适配者的方法(如果继承的是具体类而非接口),可能不符合最少知识原则。
  • 只能适配一个被适配者类。
b. 对象适配器模式 (Object Adapter)
  • 原理:通过对象的组合(关联)来实现。适配器类持有一个被适配者对象的引用,并实现了目标接口。
  • 角色
    1. Target (目标接口):同上。
    2. Adaptee (被适配者):同上。
    3. Adapter (适配器):实现 Target 接口,并内部持有一个 Adaptee 对象的实例。在实现 Target 接口的方法时,它调用 Adaptee 实例的方法。
    4. Client (客户端):同上。
      在这里插入图片描述
      优点
  • 更灵活,因为适配器可以适配 Adaptee 的任何子类。
  • 可以同时适配多个被适配者(如果需要,但不常见于标准适配器模式)。
  • 符合合成复用原则。

推荐使用对象适配器模式,因为它更符合面向对象的设计原则,具有更好的灵活性和可扩展性。

4. 适用场景 (When to Use)

  • 你想使用一个已经存在的类,而它的接口不符合你的需求。
  • 你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。
  • (仅用于对象适配器) 你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。
  • 系统集成:当需要集成一些第三方库或遗留系统,而它们的接口与当前系统不兼容时。
  • 代码迁移:在逐步替换旧组件时,可以使用适配器来兼容新旧接口。

5. 优缺点 (Pros and Cons)

通用优点:

  1. 提高类的复用性:可以复用现有的、接口不兼容的类。
  2. 增加灵活性:客户端代码与具体实现解耦,可以方便地替换被适配者或适配器。
  3. 透明性:客户端可以像使用其他符合目标接口的类一样使用适配器,无需关心适配过程。

类适配器的特定缺点:

  1. 目标抽象类只能是接口,不能是类 (在Java等单继承语言中,如果Adapter已继承Adaptee)。
  2. 不易于适配Adaptee的子类:因为Adapter已经继承了Adaptee。

对象适配器的特定优点:

  1. 可以适配Adaptee的任何子类
  2. 更容易动态替换被适配对象

通用缺点:

  1. 增加了系统的复杂性:引入了额外的适配器类。
  2. 可能降低性能:额外的间接调用可能会带来轻微的性能开销(通常可忽略不计)。

6. 实现方式 (Implementations)

我们以一个媒体播放器的例子来说明对象适配器模式。假设我们有一个通用的 MediaPlayer 接口,可以播放 mp3 文件。现在我们想让它也能播放 mp4vlc 格式的文件,但我们有一个现成的 AdvancedMediaPlayer 接口及其实现类,它们可以播放这些高级格式,但接口与 MediaPlayer 不兼容。

目标接口 (MediaPlayer)
// media_player.go
package media

// MediaPlayer 目标接口
type MediaPlayer interface {
	Play(audioType string, fileName string)
}
// MediaPlayer.java
package com.example.media;

// 目标接口
public interface MediaPlayer {
   void play(String audioType, String fileName);
}
被适配者接口 (AdvancedMediaPlayer) 和实现
// advanced_media_player.go
package media

import "fmt"

// AdvancedMediaPlayer 被适配者接口
type AdvancedMediaPlayer interface {
	PlayMp4(fileName string)
	PlayVlc(fileName string)
}

// Mp4Player 具体被适配者
type Mp4Player struct{}

func (p *Mp4Player) PlayMp4(fileName string) {
	fmt.Println("Playing mp4 file. Name:", fileName)
}
func (p *Mp4Player) PlayVlc(fileName string) { /* 什么也不做 */ }

// VlcPlayer 具体被适配者
type VlcPlayer struct{}

func (p *VlcPlayer) PlayVlc(fileName string) {
	fmt.Println("Playing vlc file. Name:", fileName)
}
func (p *VlcPlayer) PlayMp4(fileName string) { /* 什么也不做 */ }
// AdvancedMediaPlayer.java
package com.example.media;

// 被适配者接口
public interface AdvancedMediaPlayer {
   void playMp4(String fileName);
   void playVlc(String fileName);
}

// Mp4Player.java
package com.example.media.advanced;

import com.example.media.AdvancedMediaPlayer;

public class Mp4Player implements AdvancedMediaPlayer {
    @Override
    public void playMp4(String fileName) {
        System.out.println("Playing mp4 file. Name: "+ fileName);
    }

    @Override
    public void playVlc(String fileName) {
        // do nothing
    }
}

// VlcPlayer.java
package com.example.media.advanced;

import com.example.media.AdvancedMediaPlayer;

public class VlcPlayer implements AdvancedMediaPlayer {
    @Override
    public void playMp4(String fileName) {
        // do nothing
    }

    @Override
    public void playVlc(String fileName) {
        System.out.println("Playing vlc file. Name: "+ fileName);
    }
}
适配器 (MediaAdapter)
// media_adapter.go
package media

// MediaAdapter 适配器 (对象适配器)
type MediaAdapter struct {
	advancedPlayer AdvancedMediaPlayer
}

func NewMediaAdapter(audioType string) *MediaAdapter {
	adapter := &MediaAdapter{}
	if audioType == "mp4" {
		adapter.advancedPlayer = &Mp4Player{}
	} else if audioType == "vlc" {
		adapter.advancedPlayer = &VlcPlayer{}
	}
	return adapter
}

func (ma *MediaAdapter) Play(audioType string, fileName string) {
	if ma.advancedPlayer == nil {
		fmt.Printf("MediaAdapter: Cannot play %s. Advanced player not initialized for this type.\n", audioType)
		return
	}
	if audioType == "mp4" {
		ma.advancedPlayer.PlayMp4(fileName)
	} else if audioType == "vlc" {
		ma.advancedPlayer.PlayVlc(fileName)
	}
}
// MediaAdapter.java
package com.example.media;

import com.example.media.advanced.Mp4Player;
import com.example.media.advanced.VlcPlayer;

// 适配器 (对象适配器)
public class MediaAdapter implements MediaPlayer {

   AdvancedMediaPlayer advancedMusicPlayer;

   public MediaAdapter(String audioType){
      if(audioType.equalsIgnoreCase("vlc")){
         advancedMusicPlayer = new VlcPlayer();
      } else if (audioType.equalsIgnoreCase("mp4")){
         advancedMusicPlayer = new Mp4Player();
      }
      if (advancedMusicPlayer == null) {
          System.out.println("MediaAdapter: Invalid media type " + audioType + " for advanced player.");
      }
   }

   @Override
   public void play(String audioType, String fileName) {
      if(advancedMusicPlayer == null) {
          System.out.println("MediaAdapter: Cannot play " + audioType + ". Advanced player not initialized.");
          return;
      }
      if(audioType.equalsIgnoreCase("vlc")){
         advancedMusicPlayer.playVlc(fileName);
      } else if(audioType.equalsIgnoreCase("mp4")){
         advancedMusicPlayer.playMp4(fileName);
      }
   }
}
客户端使用的播放器 (AudioPlayer)
// audio_player.go
package media

import "fmt"

// AudioPlayer 客户端使用的播放器,实现了 MediaPlayer 接口
type AudioPlayer struct {
	mediaAdapter *MediaAdapter // 可以为nil,如果只播放mp3
}

func NewAudioPlayer() *AudioPlayer {
	return &AudioPlayer{}
}

func (ap *AudioPlayer) Play(audioType string, fileName string) {
	// 内置支持播放 mp3 文件
	if audioType == "mp3" {
		fmt.Println("Playing mp3 file. Name:", fileName)
	} else if audioType == "mp4" || audioType == "vlc" {
		// mediaAdapter 提供了对其他文件格式的支持
		ap.mediaAdapter = NewMediaAdapter(audioType)
		if ap.mediaAdapter.advancedPlayer != nil { // 检查适配器是否成功初始化
            ap.mediaAdapter.Play(audioType, fileName)
        } else {
            fmt.Printf("Invalid media type %s format not supported\n", audioType)
        }
	} else {
		fmt.Printf("Invalid media type %s format not supported\n", audioType)
	}
}
// AudioPlayer.java
package com.example.media;

// 客户端使用的播放器,实现了 MediaPlayer 接口
public class AudioPlayer implements MediaPlayer {
   MediaAdapter mediaAdapter;

   @Override
   public void play(String audioType, String fileName) {
      // 内置支持播放 mp3 文件
      if(audioType.equalsIgnoreCase("mp3")){
         System.out.println("Playing mp3 file. Name: "+ fileName);
      }
      // mediaAdapter 提供了对其他文件格式的支持
      else if(audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")){
         mediaAdapter = new MediaAdapter(audioType);
         if (mediaAdapter.advancedMusicPlayer != null) { // 确保适配器已为该类型初始化
            mediaAdapter.play(audioType, fileName);
         } else {
             System.out.println("Invalid media. " + audioType + " format not supported by adapter");
         }
      }
      else{
         System.out.println("Invalid media. " + audioType + " format not supported");
      }
   }
}
客户端使用
// main.go (示例用法)
/*
package main

import "./media"

func main() {
	audioPlayer := media.NewAudioPlayer()

	audioPlayer.Play("mp3", "beyond the horizon.mp3")
	audioPlayer.Play("mp4", "alone.mp4")
	audioPlayer.Play("vlc", "far far away.vlc")
	audioPlayer.Play("avi", "mind me.avi")
}
*/
// Main.java (示例用法)
/*
package com.example;

import com.example.media.AudioPlayer;

public class Main {
   public static void main(String[] args) {
      AudioPlayer audioPlayer = new AudioPlayer();

      audioPlayer.play("mp3", "beyond the horizon.mp3");
      audioPlayer.play("mp4", "alone.mp4");
      audioPlayer.play("vlc", "far far away.vlc");
      audioPlayer.play("avi", "mind me.avi");
   }
}
*/

7. 与其他模式的比较

  • 适配器模式 vs. 桥接模式 (Bridge Pattern)

    • 目的不同:适配器主要解决两个已有接口不兼容的问题(通常是事后补救);桥接模式主要目的是将抽象部分与实现部分分离,使它们可以独立变化(通常是预先设计)。
    • 时机不同:适配器通常在系统完成后,需要集成新组件或兼容旧组件时使用;桥接模式在系统设计初期就考虑分离变化。
  • 适配器模式 vs. 装饰器模式 (Decorator Pattern)

    • 目的不同:适配器是转换接口;装饰器是动态地给对象添加额外的职责(增强功能),而不改变其接口。
    • 结果不同:适配器返回的是一个符合目标接口的新对象(或包装对象);装饰器返回的是一个与原对象接口一致,但功能增强的对象。
  • 适配器模式 vs. 外观模式 (Facade Pattern)

    • 目的不同:适配器是转换接口;外观模式是为复杂的子系统提供一个简化的统一接口。
    • 处理对象数量不同:适配器通常适配一个对象;外观模式通常封装多个子系统对象。

8. 总结

适配器模式是一种非常有用的结构型模式,它像一个“万能插头”或“翻译官”,能够解决因接口不兼容而导致类无法协同工作的问题。通过引入适配器,我们可以复用现有的类,并使系统更加灵活。在实际开发中,对象适配器因其更高的灵活性而更被推荐使用。

记住它的核心:转换接口,兼容现有

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

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

相关文章

WordPress多语言插件安装与使用教程

WordPress多语言插件GTranslate的使用方法 在wordpress网站后台搜索多语言插件GTranslate并安装,安装完成、用户插件后开始设置,以下为设置方法: 1、先在后台左侧找到Gtranslate,进入到设置界面 2、选择要显示的形式&#xff0c…

欣佰特科技|SenseGlove Nova2 力反馈数据手套:助力外科手术训练的精准触觉模拟

在医疗科技持续发展的背景下,虚拟现实(VR)技术正在改变外科手术培训的方式,而 SenseGlove Nova2 力反馈数据手套 在这一领域发挥着重要作用。 SenseGlove Nova2 力反馈数据手套 与 VirtualiSurg 手术模拟系统深度结合。其手部追踪…

网络安全-等级保护(等保) 3-2-2 GB/T 28449-2019 第7章 现场测评活动/第8章 报告编制活动

################################################################################ GB/T 28449-2019《信息安全技术 网络安全等级保护测评过程指南》是规定了等级测评过程,是纵向的流程,包括:四个基本测评活动:测评准备活动、方案编制活…

IAM角色访问AWS RDS For MySQL

IAM角色访问AWS RDS For MySQL Tips: 写这篇文章,主要是用作记录;在AWS配置IAM RDS 角色权限访问,官方文档不怎么全,踩了一些坑… AWS云上配置 开启IAM身份验证 登录AWS控制台搜索并进入Databases管理页面选择数据库实例&#x…

Karakeep | 支持Docker/NAS 私有化部署!稍后阅读工具告别云端依赖,让知识收藏更有序

Karakeep 介绍 Karakeep(以前的 Hoarder)是一款开源的“Bookmark Everything”应用程序,一款基于 AI 驱动的开源书签管理工具,专为解决传统浏览器书签管理中的混乱问题而设计。其核心目标是通过智能化技术帮助用户高效整理、检索和…

RV1126+FFMPEG多路码流监控项目大体讲解

一.项目介绍: 本项目采用的是易百纳RV1126开发板和CMOS摄像头,使用的推流框架是FFMPEG开源项目。这个项目的工作流程如下(如上图):通过采集摄像头的VI模块,再通过硬件编码VENC模块进行H264/H265的编码压缩,并把压缩后的…

el-dialog 组件 多层嵌套 被遮罩问题

<el-dialog title"提示" :visible.sync"dialogBindUserVisible" width"30%" append-to-body :before-close"handleClose"> <span>这是一段信息</span> <span slot"footer" class"dialog-footer&q…

探秘谷歌Gemini:开启人工智能新纪元

一、引言 在人工智能的浩瀚星空中&#xff0c;每一次重大模型的发布都宛如一颗璀璨新星闪耀登场&#xff0c;而谷歌 Gemini 的亮相&#xff0c;无疑是其中最为耀眼的时刻之一。它的出现&#xff0c;犹如在 AI 领域投下了一颗重磅炸弹&#xff0c;引发了全球范围内的广泛关注与热…

wordcount在集群上的测试

1.将louts.txt文件从cg计算机复制到master节点上面&#xff0c;存放在/usr/local/hadoop 需要输入密码&#xff1a;83953588abc scp /root/IdeaProjects/mapReduceTest/lotus.txt root172.18.0.2:/usr/local/hadoop /WordCountTest/input 2.将lotus.txt文件从master这台机器…

OpenCV CUDA模块图像过滤------创建一个 Sobel 滤波器函数createSobelFilter()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 该函数用于创建一个 Sobel 滤波器&#xff0c;用于在 GPU 上进行边缘检测。它基于图像的梯度计算&#xff1a; dx 表示对 x 方向求导的阶数&…

PDF 转 JPG 图片小工具:CodeBuddy 助力解决转换痛点

本文所使用的 CodeBuddy 免费下载链接&#xff1a;腾讯云代码助手 CodeBuddy - AI 时代的智能编程伙伴 前言 在数字化办公与内容创作的浪潮中&#xff0c;将 PDF 文件转换为 JPG 图片格式的需求日益频繁。无论是学术文献中的图表提取&#xff0c;还是宣传资料的视觉化呈现&am…

VisionPro 与 C# 联合编程:相机连接实战指南

在工业视觉检测与自动化领域&#xff0c;康耐视&#xff08;Cognex&#xff09;的 VisionPro 是一款功能强大的视觉开发工具&#xff0c;而 C# 凭借其简洁性与高效性&#xff0c;成为许多开发者的首选编程语言。本文将详细介绍如何通过 C# 与 VisionPro 联合编程实现相机连接&a…

论文阅读:Next-Generation Database Interfaces:A Survey of LLM-based Text-to-SQL

地址&#xff1a;Next-Generation Database Interfaces: A Survey of LLM-based Text-to-SQL 摘要 由于用户问题理解、数据库模式解析和 SQL 生成的复杂性&#xff0c;从用户自然语言问题生成准确 SQL&#xff08;Text-to-SQL&#xff09;仍是一项长期挑战。传统的 Text-to-SQ…

OS面试篇

用户态和内核态 用户态和内核态的区别&#xff1f; 内核态和用户态是操作系统中的两种运行模式。它们的主要区别在于权限和可执行的操作&#xff1a; 内核态&#xff08;Kernel Mode&#xff09;&#xff1a;在内核态下&#xff0c;CPU可以执行所有的指令和访问所有的硬件资…

FFMPEG-FLV-MUX编码

一、流程图 二、结构体 1 .AVOutputFormat 一、核心功能与作用 封装格式描述 AVOutputFormat保存了输出容器格式的元数据&#xff0c;包括&#xff1a; 短名称&#xff08;name&#xff09;&#xff1a;如flv、mp4&#xff1b;易读名称&#xff08;long_name&#xff09;&…

React vs Vue.js:选哪个框架更适合你的项目?

摘要 前端开发江湖里&#xff0c;React 和 Vue.js 堪称两大 “顶流” 框架&#xff0c;不少开发者在选择时都犯了难。用 React 吧&#xff0c;听说它性能超强&#xff0c;可学习曲线也陡峭&#xff1b;选 Vue.js&#xff0c;有人夸它上手快&#xff0c;但又担心功能不够强大。…

Kafka|基础入门

文章目录 快速了解Kafka快速上手Kafka理解Kafka的集群Kafka集群的消息流转模型 快速了解Kafka 快速上手Kafka 启动zookeeper 启动kafka 创建topic - 启动发送者 - 启动消费者 Partition 0: [msg1] -> [msg2] -> [msg3] -> ...0 1 2Partition 1: [msg4…

ADS学习笔记(五) 谐波平衡仿真

参考书籍:见资源绑定,书籍4.2 谐波平衡仿真 本文为对实验内容的补充 1. 三阶交调点坐标系图分析 我们来分析图1.5中“三阶交调点”坐标系图里的两条直线分别代表什么。 图中有两条向上倾斜的直线&#xff1a; 斜率较低的那条直线代表&#xff1a;基波输出功率 (Fundamental Out…

PETR- Position Embedding Transformation for Multi-View 3D Object Detection

旷视 ECCV 2022 纯视觉BEV方案transformer网络3D检测 paper&#xff1a;[2203.05625] PETR: Position Embedding Transformation for Multi-View 3D Object Detection code&#xff1a;GitHub - megvii-research/PETR: [ECCV2022] PETR: Position Embedding Transformation …

Prompt Tuning与自然语言微调对比解析

Prompt Tuning 与输入提示词自然语言微调的区别和联系 一、核心定义与区别 维度Prompt Tuning(提示微调)输入提示词自然语言微调本质优化连续向量空间中的提示嵌入(不可直接阅读)优化离散自然语言文本(人类可理解)操作对象模型输入嵌入层的连续向量(如WordEmbedding)自…