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

Golang 适配器模式与外观模式 — 结构型设计模式入门

适配器模式与外观模式 — 结构型设计模式入门


一、结构型设计模式概述

创建型模式解决"怎么创建对象"的问题,而结构型模式解决"怎么组合对象"的问题。它们关注的是类和对象之间的关系与协作方式——如何把不同的模块拼在一起,让它们配合工作而不互相干扰。

结构型模式一共有七种:适配器、外观、代理、装饰器、组合、享元、桥接。今天先学前两个。


二、适配器模式(Adapter Pattern)

问题场景

你正在开发一个日志系统,系统内部统一使用 Logger 接口(方法名是 Log(msg string))。但你想接入一个第三方日志库,它提供的方法叫 Write(entry string)——名字不同,签名看起来相似,但无法直接替换。

你不能修改第三方库的代码(它不在你的控制范围内),也不能强行改自己的接口(那会影响系统其他部分)。怎么办?

核心思想

适配器模式就像现实中的电源转换器:中国的插头和欧洲的插座形状不匹配,但你加一个转换器就能接上。适配器在不修改原有接口的前提下,把一个接口转换成另一个接口

做法很简单:创建一个中间层结构体,它实现你想要的接口,内部持有那个"不兼容"的对象,把接口调用翻译成对方的方法。

Go 实现:日志系统适配器

package mainimport "fmt"// Target 是我们系统期望的日志接口
type Logger interface {Log(msg string)
}// Adaptee 是第三方库提供的接口(不兼容)
type ThirdPartyWriter struct{}func (w *ThirdPartyWriter) Write(entry string) {fmt.Printf("[ThirdParty] %s\n", entry)
}// Adapter 持有 ThirdPartyWriter,实现 Logger 接口
type LoggerAdapter struct {writer *ThirdPartyWriter
}func (a *LoggerAdapter) Log(msg string) {// 把 Log() 翻译成 Write()a.writer.Write(msg)
}func main() {// 直接用 ThirdPartyWriter 是不行的,它没有 Log 方法// 但通过适配器就可以无缝接入var logger Logger = &LoggerAdapter{writer: &ThirdPartyWriter{}}logger.Log("系统启动完成")logger.Log("用户登录成功")
}

运行输出(预期):

[ThirdParty] 系统启动完成
[ThirdParty] 用户登录成功

适配器的两种形态

类适配器(Go 不支持):通过继承同时实现 Target 接口和 Adaptee 类。Go 没有继承,所以做不了。

对象适配器(Go 唯一可用):组合——Adapter 持有 Adaptee 实例,实现 Target 接口,在方法中调用 Adaptee 的方法。这也是 Go 里最自然的方式,因为 Go 本身就推崇组合优于继承。

适配器 vs 代理 vs 装饰器

这三个模式的结构看起来很像(都是中间层持有一个对象并转发调用),但意图完全不同:

模式 意图 改变接口? 改变行为?
适配器 让不兼容的接口能一起工作 (接口名/签名变了) 否(只是翻译)
代理 控制访问(权限、延迟加载、缓存) 否(控制访问而非增强)
裬饰器 动态增加功能 否(接口不变) (加了新能力)

什么时候用适配器模式

  • 需要使用一个已有类/库,但它的接口和你系统的不一致
  • 你不能修改对方代码(第三方库、遗留系统)
  • 需要兼容多个不同接口的实现(比如多种数据库驱动适配同一接口)

三、外观模式(Facade Pattern)

问题场景

一个电商系统下单流程涉及五个子系统:库存检查、价格计算、优惠券验证、支付处理、物流下单。客户端要完成一个下单操作,需要依次调用五个子系统的方法,还要处理它们之间的依赖和错误。调用方被这些复杂细节淹没了。

核心思想

外观模式就是给复杂子系统披上一层"外衣"——提供一个简单的、统一的入口方法,把内部的多个子系统调用封装起来,调用方只需要和这个外观打交道

就像你去银行办业务:你不需要分别和柜台、信贷部、风控部、合规部打交道,你只要找大堂经理说"我要办一笔贷款",她就在内部协调所有部门。

Go 实现:电商下单外观

package mainimport "fmt"// ========== 子系统 ==========type InventorySystem struct{}func (i *InventorySystem) Check(productID string) bool {fmt.Printf("  [库存] 检查 %s 库存...\n", productID)return true // 假设库存充足
}type PricingSystem struct{}func (p *PricingSystem) Calculate(productID string, quantity int) float64 {fmt.Printf("  [价格] 计算 %s × %d 的价格...\n", productID, quantity)return 99.9 * float64(quantity)
}type CouponSystem struct{}func (c *CouponSystem) Validate(code string) float64 {fmt.Printf("  [优惠券] 验证优惠码 %s...\n", code)if code == "SAVE10" {return 0.1 // 10% 折扣}return 0 // 无折扣
}type PaymentSystem struct{}func (p *PaymentSystem) Process(amount float64) bool {fmt.Printf("  [支付] 处理支付 ¥%.2f...\n", amount)return true
}type LogisticsSystem struct{}func (l *LogisticsSystem) Ship(productID string, quantity int) {fmt.Printf("  [物流] 安排发货 %s × %d...\n", productID, quantity)
}// ========== 外观角色 ==========type OrderFacade struct {inventory *InventorySystempricing   *PricingSystemcoupon    *CouponSystempayment   *PaymentSystemlogistics *LogisticsSystem
}func NewOrderFacade() *OrderFacade {return &OrderFacade{inventory: &InventorySystem{},pricing:   &PricingSystem{},coupon:    &CouponSystem{},payment:   &PaymentSystem{},logistics: &LogisticsSystem{},}
}// PlaceOrder 是外观提供的简化接口,一键下单
func (f *OrderFacade) PlaceOrder(productID string, quantity int, couponCode string) bool {fmt.Println("--- 开始下单流程 ---")// 1. 检查库存if !f.inventory.Check(productID) {fmt.Println("下单失败:库存不足")return false}// 2. 计算价格totalPrice := f.pricing.Calculate(productID, quantity)// 3. 验证优惠券discount := f.coupon.Validate(couponCode)finalPrice := totalPrice * (1 - discount)fmt.Printf("  [汇总] 原价 ¥%.2f,优惠 %.0f%%,应付 ¥%.2f\n",totalPrice, discount*100, finalPrice)// 4. 支付if !f.payment.Process(finalPrice) {fmt.Println("下单失败:支付失败")return false}// 5. 发货f.logistics.Ship(productID, quantity)fmt.Println("--- 下单成功 ---")return true
}func main() {facade := NewOrderFacade()// 客户端只需调用一个方法,不用关心五个子系统的细节facade.PlaceOrder("SKU-001", 2, "SAVE10")
}

运行输出(预期):

--- 开始下单流程 ---[库存] 检查 SKU-001 库存...[价格] 计算 SKU-001 × 2 的价格...[优惠券] 验证优惠码 SAVE10...[汇总] 原价 ¥199.80,优惠 10%,应付 ¥179.82[支付] 处理支付 ¥179.82...[物流] 安排发货 SKU-001 × 2...
--- 下单成功 ---

外观模式的要点

  1. 不增加新功能:外观只是把已有子系统的功能重新编排了一遍,并没有创造出子系统做不到的事情。
  2. 不阻止直接访问子系统:如果调用方有时需要细粒度控制,仍然可以直接调用子系统方法。外观是"简化入口",不是"唯一入口"。
  3. 可以有多层外观:大外观可以包含小外观。比如"下单"外观内部可能用了"支付"外观(支付外观又封装了银行卡验证、余额检查等)。

外观模式在实际项目中的身影

  • API Gateway:微服务架构中的网关就是外观——客户端只和网关通信,网关在内部路由到各个微服务
  • ORM:GORM 封装了 SQL 连接、语句构建、结果映射等复杂操作,暴露出简单的 Create/Find/Update/Delete 方法
  • 标准库 os/execCommand.Run() 内部处理了进程创建、管道设置、信号处理,调用方只需一行代码

什么时候用外观模式

适用 不适用
子系统复杂,调用方只需高层操作 子系统简单,直接调用就行
需要隔离子系统变化对客户端的影响 调用方需要频繁使用子系统的不同方法组合
多个客户端共享同一套子系统调用流程 每个客户端的调用流程差异很大

四、适配器 + 外观组合使用

在实际项目中,这两种模式经常一起出现。比如对接一个第三方支付 SDK:

  1. 适配器:把第三方 SDK 的接口适配成你系统的 PaymentProvider 接口
  2. 外观:在你的 PaymentFacade 中封装"查询余额→发起支付→确认结果"的完整流程
// 适配器让第三方 SDK 适配你的接口
type StripeAdapter struct {stripeClient *StripeSDK
}
func (a *StripeAdapter) Pay(amount float64) bool {return a.stripeClient.Charge(int(amount * 100)) // SDK 用分而不是元
}// 外观封装完整支付流程
type PaymentFacade struct {provider PaymentProvideraudit    *AuditSystem
}
func (f *PaymentFacade) QuickPay(amount float64) bool {f.audit.LogPayStart(amount)result := f.provider.Pay(amount)f.audit.LogPayResult(result)return result
}

调用方只和 PaymentFacade 交互,既不用管 Stripe SDK 的接口差异,也不用管审计系统的调用时机。


五、本章小结

  • 结构型模式关注"怎么组合对象",今天学了适配器和外观两种
  • 适配器:让不兼容接口协同工作,不修改任何一方代码,只加中间层
  • 外观:为复杂子系统提供简化入口,封装内部调用流程
  • 适配器改变接口(翻译),外观简化接口(打包),代理控制接口(权限)
  • 两者常组合使用:适配器统一接口 + 外观简化流程
http://www.jsqmd.com/news/1292959/

相关文章:

  • Blender Python自动化:代码驱动角色嘴部骨骼与形态键动画
  • 2026污水处理厂淤泥干化土工管袋选型5大维度对比指南 - 博客万
  • JetBrains IDE试用重置终极指南:30天无限续期的简单方法
  • 技术视角下的随身WiFi实名认证:不是“麻烦”,是“刚需”!随身wifi实名认证有必要吗?
  • 学即所考:编程猫与猿编程自研工具的竞赛认可度对比 - 资讯在线
  • 呼和浩特托克托黄金回收|闲置婚嫁金、投资金条变现避坑指南 - 淡泊明志。
  • 2026老婆饼用户评价好的品牌大盘点:正规合规实力强口碑产品详解 兼服务商选型避坑FAQ大全 - 产业观察报
  • 2026年南明区正规私立高中收费究竟是多少呢? - 企业推荐官
  • 2026焦作全屋定制厂家推荐:水深似海,实体源头工厂硬核突围 - 兔兔不是荼荼
  • Altium Designer导入嘉立创EDA元件库:原理图、封装与3D模型迁移全攻略
  • 2026专业的淮安装修公司推荐 附避坑注意事项 - 博客万
  • 购房收据丢失登报实用教程,确权必备遗失声明,范本直接套用 - 叮咚办真方便
  • C语言入门指南:从基础语法到核心概念
  • 雁塔区业主必看!2026西安本地化防水,告别反复渗漏/漏水 - 吉林同城获客
  • 图像批处理脚本工程化:从单张测试到批量稳定的实战指南
  • 行业前列与学科基因:编程猫与学而思编程的品牌护城河在哪里? - 资讯在线
  • BurpJSLinkFinder配置与实战:从Jython环境到JS链接挖掘
  • 2026新经济赛道互联网大厂高端人才招聘网站选型全指南:企业与人才双向适配、靠谱平台盘点及签约避坑全攻略FAQ - 行业观察网
  • 预警!宜春黄金抛售窗口期仅剩最后几天?最新回收价曝光,这些店最“称”心! - 黄金珠宝
  • 2026 青岛闲置包包变现痛点:磨损污渍五金老化,对应回收价位 - 奢侈品回收探店ing
  • 2026孕妇食用酸枣糕品牌选型全解析:合规资质、营养适配避坑指南及正规品牌实操攻略 - 行业观察网
  • 新西兰NZTA认证翻译怎么办理?新西兰NZTA翻译件长什么样? - 叮咚办真方便
  • 2026年电爪品牌怎么选?主流电爪厂家品牌盘点 - 品牌深度评测
  • 2026年宁波企业查阅ERP管理系统名录实用场景分享 - 起跑123
  • 大模型中的token机制:原理、应用与优化实践
  • 3分钟掌握Godot游戏PCK文件解包:官方工具实战与逆向学习指南
  • OpenClaw:模块化AI代理框架与群体智能实践
  • 2026年辽吉黑/锡林郭勒/鄂尔多斯:耐高压420/高品质316L/304/321不锈钢棒现货及供销加工 - 硬核推荐
  • RAG Agent 检索管线:查询改写、混合召回与证据压缩
  • 2026天津防水公司推荐榜 不同预算需求适配指南 - 博客万