Go语言设计模式:适配器模式详解 - 实践

news/2025/11/27 21:58:27/文章来源:https://www.cnblogs.com/yangykaifa/p/19279565

文章目录

    • 一、适配器模式概述
      • 1.1 什么是适配器模式?
      • 1.2 建造者模式的优缺点
      • 1.3 适用场景
      • 1.4 适配器模式的UML图与核心角色
    • 二、 Go语言实现:对象适配器
      • 第1步:定义目标接口
      • 第2步:定义被适配者
      • 第3步:创建适配器
      • 第4步:客户端使用
      • 对象适配器模式(完整版)
    • 三、Go语言实现:类适配器
      • 第1步和第2步:目标接口和被适配者(同上)
      • 第3步:创建类适配器(通过嵌入)
      • 第4步:客户端使用
      • 类适配器模式(完整版)
    • 四、Go语言实现:一个更实际的例子
      • 第1步:定义目标接口
      • 第2步:定义被适配者(第三方库)
      • 第3步:创建适配器
      • 第4步:客户端使用
      • 实际应用示例:日志系统适配器(完整版)

一、适配器模式概述

1.1 什么是适配器模式?

适配器模式是一种结构型设计模式,它能使接口不兼容的对象能够相互合作。适配器模式就像一个中间人,它充当两个不同接口之间的桥梁,使得一个类的接口能够满足客户端的期望,而无需修改原始类的代码。现实生活中的比喻:

1.2 建造者模式的优缺点

优点

  • 单一职责原则:你可以将接口转换代码从业务逻辑中分离出来,使代码结构更清晰。
  • 开闭原则:你可以在不修改现有客户端代码的情况下,引入新的适配器来兼容新的接口。
  • 解耦:客户端和被适配者之间没有直接耦合,它们都依赖于抽象(目标接口)。

缺点

  • 增加代码复杂性:引入了新的类(适配器),可能会使代码结构变得更复杂,尤其是在适配逻辑很简单的情况下。
  • 性能开销:适配器转换过程可能会带来一些额外的性能开销,但通常可以忽略不计。

1.3 适用场景

1.4 适配器模式的UML图与核心角色

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

  • Client(客户端):与符合 Target 接口的对象协同工作。
  • Target(目标接口):客户端所期望的接口。
  • Adaptee(被适配者):一个已经存在的、接口不兼容的类,需要被适配。
  • Adapter(适配器):实现 Target 接口,并持有一个 Adaptee 的实例。它将 Target 接口的调用转换为对 Adaptee 接口的调用。
    UML 类图:
+---------+       +----------------+       +-----------------+
| Client  |------>|   Target       |<------|    Adapter      |
+---------+       +-----------------+     | (implements)    || + Request()    |<-----+-----------------++-----------------+     | - adaptee: Adaptee|+-----------------+| + Request()     |+-----------------+| usesv+-------------+|   Adaptee   |+-------------+| + SpecificRequest() |+-------------+

二、 Go语言实现:对象适配器

对象适配器是最常用的一种实现方式。它通过组合(持有被适配者的实例)来实现适配,而不是通过继承。这完全符合Go语言“组合优于继承”的哲学。
我们用一个简单的例子来说明:我们有一个 LegacyPrinter(被适配者),它只有一个 PrintOld 方法。但我们的新系统(客户端)期望使用一个 ModernPrinter 接口(目标接口),该接口有一个 Print 方法。

第1步:定义目标接口

// Target: 现代打印机接口,客户端期望的接口
type ModernPrinter interface {
Print(msg string)
}

第2步:定义被适配者

// Adaptee: 一个旧的打印机,接口不兼容
type LegacyPrinter struct{}
func (lp *LegacyPrinter) PrintOld(s string) {
fmt.Println("Legacy Printer:", s)
}

第3步:创建适配器

// Adapter: 适配器,将旧打印机适配成现代打印机接口
type LegacyPrinterAdapter struct {
legacyPrinter *LegacyPrinter
}
// NewLegacyPrinterAdapter 是适配器的构造函数
func NewLegacyPrinterAdapter(lp *LegacyPrinter) *LegacyPrinterAdapter {
return &LegacyPrinterAdapter{legacyPrinter: lp}
}
// Print 实现了 ModernPrinter 接口
func (lpa *LegacyPrinterAdapter) Print(msg string) {
// 在这里可以进行一些转换逻辑
formattedMsg := "Adapter: " + msg
// 调用被适配者的方法
lpa.legacyPrinter.PrintOld(formattedMsg)
}

第4步:客户端使用

func main() {
// 1. 创建一个被适配者实例
oldPrinter := &LegacyPrinter{}
// 2. 创建适配器,并将被适配者注入
adapter := NewLegacyPrinterAdapter(oldPrinter)
// 3. 客户端通过目标接口来使用适配器
// 客户端不需要知道它实际在和 LegacyPrinter 打交道
printMessage(adapter, "Hello, World!")
}
// Client: 一个函数,它只关心 ModernPrinter 接口
func printMessage(p ModernPrinter, msg string) {
p.Print(msg)
}

对象适配器模式(完整版)

这是最常用、最推荐的实现方式。 文件:object_adapter.go

package main
import "fmt"
// ======================
// 1. 定义目标接口
// ======================
// Target: 现代打印机接口,客户端期望的接口
type ModernPrinter interface {
Print(msg string)
}
// ======================
// 2. 定义被适配者
// ======================
// Adaptee: 一个旧的打印机,接口不兼容
type LegacyPrinter struct{}
func (lp *LegacyPrinter) PrintOld(s string) {
fmt.Println("Legacy Printer:", s)
}
// ======================
// 3. 创建适配器
// ======================
// Adapter: 适配器,将旧打印机适配成现代打印机接口
type LegacyPrinterAdapter struct {
legacyPrinter *LegacyPrinter
}
// NewLegacyPrinterAdapter 是适配器的构造函数
func NewLegacyPrinterAdapter(lp *LegacyPrinter) *LegacyPrinterAdapter {
return &LegacyPrinterAdapter{legacyPrinter: lp}
}
// Print 实现了 ModernPrinter 接口
func (lpa *LegacyPrinterAdapter) Print(msg string) {
// 在这里可以进行一些转换逻辑
formattedMsg := "Adapter: " + msg
// 调用被适配者的方法
lpa.legacyPrinter.PrintOld(formattedMsg)
}
// ======================
// 4. 客户端使用
// ======================
// Client: 一个函数,它只关心 ModernPrinter 接口
func printMessage(p ModernPrinter, msg string) {
p.Print(msg)
}
func main() {
fmt.Println("--- Running Object Adapter Example ---")
// 1. 创建一个被适配者实例
oldPrinter := &LegacyPrinter{}
// 2. 创建适配器,并将被适配者注入
adapter := NewLegacyPrinterAdapter(oldPrinter)
// 3. 客户端通过目标接口来使用适配器
// 客户端不需要知道它实际在和 LegacyPrinter 打交道
printMessage(adapter, "Hello, World!")
}

执行结果:

$ go run object_adapter.go
--- Running Object Adapter Example ---
Legacy Printer: Adapter: Hello, World!

在这个例子中,printMessage 函数(客户端)成功调用了 LegacyPrinter 的功能,而这一切都通过 LegacyPrinterAdapter 这个“中间人”无缝地完成了。

三、Go语言实现:类适配器

类适配器通过多重继承来实现。在Go语言中,没有传统的类继承,但我们可以通过结构体嵌入来模拟类似的效果。
类适配器会同时继承(嵌入)Target 接口和 Adaptee 结构体。这种方式在Go中比较少见,因为它不如对象适配器灵活。
我们继续用上面的例子。

第1步和第2步:目标接口和被适配者(同上)

// Target
type ModernPrinter interface {
Print(msg string)
}
// Adaptee
type LegacyPrinter struct{}
func (lp *LegacyPrinter) PrintOld(s string) {
fmt.Println("Legacy Printer:", s)
}

第3步:创建类适配器(通过嵌入)

// Adapter: 通过嵌入来模拟类适配器
// 注意:这种写法在Go中并不常见,也不够灵活
type ClassAdapter struct {
*LegacyPrinter // 嵌入被适配者,相当于“继承”了它的方法
}
// Print 实现了 ModernPrinter 接口
func (ca *ClassAdapter) Print(msg string) {
// 直接调用嵌入结构体的方法
ca.PrintOld("Class Adapter: " + msg)
}

第4步:客户端使用

func main() {
// 直接创建类适配器实例
classAdapter := &ClassAdapter{}
// 客户端代码与之前完全一样
printMessage(classAdapter, "Hello from Class Adapter!")
}
func printMessage(p ModernPrinter, msg string) {
p.Print(msg)
}

类适配器模式(完整版)

这是通过结构体嵌入模拟的实现方式,在Go中不常用。
文件:class_adapter.go

package main
import "fmt"
// ======================
// 1. 定义目标接口
// ======================
// Target: 现代打印机接口,客户端期望的接口
type ModernPrinter interface {
Print(msg string)
}
// ======================
// 2. 定义被适配者
// ======================
// Adaptee: 一个旧的打印机,接口不兼容
type LegacyPrinter struct{}
func (lp *LegacyPrinter) PrintOld(s string) {
fmt.Println("Legacy Printer:", s)
}
// ======================
// 3. 创建类适配器 (通过嵌入)
// ======================
// Adapter: 通过嵌入来模拟类适配器
// 注意:这种写法在Go中并不常见,也不够灵活
type ClassAdapter struct {
*LegacyPrinter // 嵌入被适配者,相当于“继承”了它的方法
}
// Print 实现了 ModernPrinter 接口
func (ca *ClassAdapter) Print(msg string) {
// 直接调用嵌入结构体的方法
ca.PrintOld("Class Adapter: " + msg)
}
// ======================
// 4. 客户端使用
// ======================
// Client: 一个函数,它只关心 ModernPrinter 接口
func printMessage(p ModernPrinter, msg string) {
p.Print(msg)
}
func main() {
fmt.Println("--- Running Class Adapter Example ---")
// 直接创建类适配器实例
classAdapter := &ClassAdapter{}
// 客户端代码与之前完全一样
printMessage(classAdapter, "Hello from Class Adapter!")
}

执行结果:

$ go run class_adapter.go
--- Running Class Adapter Example ---
Legacy Printer: Class Adapter: Hello from Class Adapter!

为什么类适配器在Go中不常用?

  • 耦合度高:适配器与被适配者静态地绑定在一起,无法在运行时更换被适配者。
  • 灵活性差:对象适配器可以适配 LegacyPrinter 的任何子类,而类适配器则做不到。
  • 不符合Go哲学:Go推崇组合和显式依赖注入,而类适配器的嵌入方式更像是一种“继承”的变体,不够清晰。

四、Go语言实现:一个更实际的例子

假设我们正在开发一个日志系统,我们有一个统一的 Logger 接口(目标)。现在,我们想集成一个第三方的日志库 ThirdPartyLogger(被适配者),但它的接口是 Log

第1步:定义目标接口

// Target: 我们系统统一的日志接口
type Logger interface {
Info(message string)
}

第2步:定义被适配者(第三方库)

// Adaptee: 第三方日志库,我们无法修改它的代码
type ThirdPartyLogger struct{}
func (tpl *ThirdPartyLogger) Log(level, message string) {
fmt.Printf("[ThirdParty] %s: %s\n", level, message)
}

第3步:创建适配器

// Adapter: 将第三方日志库适配到我们的Logger接口
type ThirdPartyLoggerAdapter struct {
thirdPartyLogger *ThirdPartyLogger
}
func NewThirdPartyLoggerAdapter(tpl *ThirdPartyLogger) *ThirdPartyLoggerAdapter {
return &ThirdPartyLoggerAdapter{thirdPartyLogger: tpl}
}
// Info 实现了我们的 Logger 接口
func (tpla *ThirdPartyLoggerAdapter) Info(message string) {
// 将我们的 Info 调用转换为第三方的 Log 调用
tpla.thirdPartyLogger.Log("INFO", message)
}

第4步:客户端使用

func main() {
// 我们的系统需要使用 Logger 接口
var logger Logger
// 现在我们想用第三方库,通过适配器进行适配
thirdPartyLogger := &ThirdPartyLogger{}
logger = NewThirdPartyLoggerAdapter(thirdPartyLogger)
// 系统的其余部分可以无差别地使用 logger
LogInfo(logger, "System started successfully.")
}
// Client: 系统中的一个函数,它只依赖 Logger 接口
func LogInfo(l Logger, msg string) {
l.Info(msg)
}

实际应用示例:日志系统适配器(完整版)

这个例子更贴近真实世界的开发场景。
文件:logger_adapter.go

package main
import "fmt"
// ======================
// 1. 定义目标接口
// ======================
// Target: 我们系统统一的日志接口
type Logger interface {
Info(message string)
}
// ======================
// 2. 定义被适配者 (第三方库)
// ======================
// Adaptee: 第三方日志库,我们无法修改它的代码
type ThirdPartyLogger struct{}
func (tpl *ThirdPartyLogger) Log(level, message string) {
fmt.Printf("[ThirdParty] %s: %s\n", level, message)
}
// ======================
// 3. 创建适配器
// ======================
// Adapter: 将第三方日志库适配到我们的Logger接口
type ThirdPartyLoggerAdapter struct {
thirdPartyLogger *ThirdPartyLogger
}
func NewThirdPartyLoggerAdapter(tpl *ThirdPartyLogger) *ThirdPartyLoggerAdapter {
return &ThirdPartyLoggerAdapter{thirdPartyLogger: tpl}
}
// Info 实现了我们的 Logger 接口
func (tpla *ThirdPartyLoggerAdapter) Info(message string) {
// 将我们的 Info 调用转换为第三方的 Log 调用
tpla.thirdPartyLogger.Log("INFO", message)
}
// ======================
// 4. 客户端使用
// ======================
// Client: 系统中的一个函数,它只依赖 Logger 接口
func LogInfo(l Logger, msg string) {
l.Info(msg)
}
func main() {
fmt.Println("--- Running Real-World Logger Adapter Example ---")
// 我们的系统需要使用 Logger 接口
var logger Logger
// 现在我们想用第三方库,通过适配器进行适配
thirdPartyLogger := &ThirdPartyLogger{}
logger = NewThirdPartyLoggerAdapter(thirdPartyLogger)
// 系统的其余部分可以无差别地使用 logger
LogInfo(logger, "System started successfully.")
}

执行结果:

$ go run logger_adapter.go
--- Running Real-World Logger Adapter Example ---
[ThirdParty] INFO: System started successfully.

这个例子完美地展示了适配器模式的实际价值:在不修改现有代码(包括客户端和第三方库)的情况下,让两者协同工作。

总结:适配器模式是一个非常实用且常见的设计模式,它的核心是转换接口,解决不兼容问题。在Go语言中:

  1. 对象适配器是首选实现方式。它利用Go的组合特性,将适配器与被适配者解耦,非常灵活,符合Go的编程哲学。
  2. 类适配器可以通过结构体嵌入来模拟,但由于其高耦合和低灵活性,在实际Go开发中很少使用。
  3. 函数式适配器:在某些简单场景下,适配器甚至可以是一个函数。例如,func AdapterFunc(s string) { oldPrinter.PrintOld(s) }。如果目标接口只有一个方法,这种方式非常简洁。

当你遇到以下情况时,应该首先想到适配器模式:

  • 集成第三方库或遗留系统。
  • 需要统一多个不同接口的类,让它们能被客户端以同样的方式处理。
    掌握适配器模式,能让你在处理系统集成和接口兼容性问题时更加得心应手。

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

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

相关文章

20251127周四日记

20251127周四日记今日: 1.早上学了pytorch,对于计算机视觉无非就是几项: import一堆-定义transforms-dataset-dataloader-model(nn.Module)-criterion-optimization-epochnum-训练(for循环-梯度清零-前向传播-计算…

【第一周:Python 测试开发核心错题集 避坑指南】

📘 第一周:Python 核心与工程规范错题本 1. ☠️ 必死题:可变默认参数 (Mutable Default Arguments)你的错误:认为 def func(l=[]): 里的 [] 每次调用都会重置。 真相:函数默认参数在 定义时 (Definition Time) …

空间够造+花钱够省!红旗HS6霸榜家用大五座混动推荐

2025年底选家用车这么难?要么7座第三排“狗都不坐”,要么主驾堆满配置后排像“二等座”,续航标着1000km实际跑一半就亮灯……别骂了,我懂你搜“国产家用大五座混动SUV推荐”“适合家用的智驾SUV推荐”时的崩溃。好…

搜维尔科技:为什么选择Xsens动作捕捉作为人形机器人解决方案?

帮助人形机器人像人类一样行走、奔跑和恢复 人形机器人正在走出实验室,进入现实世界。为了像人类一样行走、奔跑和恢复,它们需要的不仅仅是预先设定的动作程序—它们还需要可靠的平衡控制。Xsens工业级IMU可提供精确…

题解:P13266 [GCJ 2014 Finals] Symmetric Trees

更差的阅读体验NOIP2025 RP++ 喵。考虑哈希。 假设 \(c_i\) 为节点 \(i\) 的颜色。 首先为了避免冲突,我们将 \(c_i\) 映射成随机大整数。然后我们构造一个哈希函数,既能体现出树的形态,又能体现点的颜色。可以这样…

python---深拷贝浅拷贝

这是笔记了啦,有点乱,感觉面试会必背赋值 lis1 = [1,2,3,4,5] lis2 = lis1 #将lis1赋值给lis2 #给lis1新增元素 lis1.append(6) print(新增后的lis1,lis1) print(新增后的lis2,lis2) #新增后的lis1 [1, 2, 3, 4, 5,…

解决ARM Linux下使用PlatformIO 找不到编译工具链toolchain报错

问题描述 在ARM架构的Debian12上使用PlatformIO开发STM32遇到如下报错PIO Core Call Error: "Platform Manager: Installing ststm32\nPlatform Manager: ststm32 @ 10.0.1 has been installed!\nTool Manager: I…

搜维尔科技:新一代Xsens Link动作捕捉系统,非常适合实时机器人远程操控、虚拟制作和现场演出录制

值得信赖的动作捕捉标准,经过重新设计,可适应人类的各种运动方式。十多年来,Xsens Link为包括人形机器人、电影、生物力学、游戏开发和运动表现等在内的众多行业提供动作捕捉技术支持。 凭借其卓越的性能,Xsens Li…

logback日期格式

通过在 Logback 的 pattern 中使用 %d{yyyy-MM-dd HH:mm:ss.SSS, XXX} 配置的: 具体配置方法 <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS, XXX} [%thread] %-5level %logger{36} - %msg%n</pattern>完整的 log…

Codeforces Round 1066 (Div. 1 + Div. 2) 比赛总结

比赛链接 Result爽掉 \(11\) 分,再这么打下去要掉成青了…… Solution D - Billion Players Game 可以发现绝对值没有任何用,第一种操作收益一定为 \(a_i-p\),第二种操作收益一定为 \(p-a_i\)。把 \(a_{1\sim n}\) …

10424_基于Springboot的物流管理系统

1、项目包含 项目源码、项目文档、数据库脚本、软件工具等资料; 带你从零开始部署运行本套系统。 2、项目介绍 随着社会的发展,计算机的优势和普及使得智能物流管理系统的开发成为必需。智能物流管理系统主要是借助计…

解决VirtualBox - Error In supR3HardenedWinReSpawn报错

解决步骤选择Virtualbox目录下的\drivers\vboxsup\VBoxSup.inf(旧版本是VBoxDrv.inf),右键选择安装打开注册表编辑器打开路径HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\VBoxSup#旧版本是 HKEY_LOCAL_M…

1127随笔

这几天上网上查了下怎么整ai接口 最后也算是完成了调用免费ai创建故事与生成图片,并成功保存在数据库里。 要说难点的话,应该就是功能在网页上的集成吧。

大规模微服务强大的系统中的雪崩故障防治

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

flutter 生命周期管理:从 Widget 到 State 的完整解析 - 指南

flutter 生命周期管理:从 Widget 到 State 的完整解析 - 指南2025-11-27 21:27 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !impor…

python写入csv代码

import csv import osdef save_to_csv(book_list):with open(books.csv,w,newline=, encoding=utf-8-sig) as csvfile:writer=csv.DictWriter(csvfile,fieldnames=[书名, 作者, ISBN, 价格,库存])writer.writeheader()…

gradle的各个环境依赖jar包的同一个版本导致的严重后果

背景:我司的A项目(gradle)、B项目(maven),且公司无maven私服,jar包均放在gitlab的项目的package registry中A项目依赖B项目的api包,我的做法如下:B项目版本定义成1.0-SNAPSHOT版本,永远都是这个版本 在gitla…

20251127

我这个忘浑蛋又忘了写博客了,最近想弄个远程服务器试试javaweb,阿里云好像有学生特权,一会儿去看看。

一定要扪心自问

你如果还只是理解的那么片面,学了那么多有什么用 一定要思考 就像这道题吧: 1.min考虑的是初始值0x3f3f3f3f; 2.选了和没选而不是无脑的dp[][]; 不可以再像之前一样永远无脑的dp[][]!!!; #include <bits/stdc++.h…

2025年租房APP推荐:官方测评与精选攻略

2025年租房APP推荐:官方测评与精选攻略在城市青年的居住选择谱系中,租房早已不再是“找个落脚处”那么简单。信息冗杂的品牌公寓、良莠不齐的中介平台、真假难辨的个人房源,将租客推向一场耗时耗力的拉锯战。人们不…