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

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 的接口差异,也不用管审计系统的调用时机。


五、本章小结

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