Facade 是封装子系统调用链路的结构体,仅当需固定顺序调用多步骤且参数重复时才自定义;Go 中通过非导出字段+组合方法实现,依赖注入 via 构造函数,方法应返回业务错误、参数按稳定性决定是否打包为 struct,测试须覆盖回滚、并发与参数传递。

Facade 是什么,什么时候该自己写一个
不是所有子系统都需要 Facade;只有当你发现调用链路频繁出现「必须按固定顺序调用 A→B→C,且每次都要传一堆重复参数」时,才值得封装。Go 本身没有 interface-based 的抽象强制要求,所以 Facade 在 Go 里本质是个结构体 + 一组组合方法,不涉及继承或动态分发。
- 典型场景:
PaymentService调用前要先验签、再查余额、再锁库存、最后发消息——这四步在业务层反复出现 - 别硬套:如果只是两个函数简单组合(比如
json.Marshal+http.Post),直接写个工具函数更轻量 - 注意副作用:Facade 方法内部不该隐藏状态变更(比如缓存初始化),否则测试和并发会出问题
怎么定义 Facade 结构体,字段要不要导出
Facade 结构体字段通常只保留子系统核心依赖,且**全部小写、不导出**。对外暴露的只是方法,不是内部组件。导出字段等于把耦合面主动敞开,后续换掉某个子系统实现时,调用方代码就可能崩。
- 正确做法:
type OrderFacade struct { payment *PaymentClient stock *StockService notify NotificationSender } - 错误示范:
Payment *PaymentClient(大写导出)——调用方可能绕过 Facade 直接操作Payment,破坏封装 - 依赖注入用构造函数:通过
NewOrderFacade(payment, stock, notify)传入,别用全局变量或 init 初始化
方法签名设计:要不要返回 error,参数要不要打包成 struct
Facade 方法是业务语义的入口,不是底层 API 的镜像。它该返回明确的业务错误(如 ErrInsufficientBalance),而不是把 io.EOF 或 redis.Timeout 原样透出。参数是否打包,取决于变化频率。
- 参数少且稳定(≤3 个):直接列出来,比如
PlaceOrder(userID string, itemID string, count int) - 参数多或易变:用选项 struct,例如
type PlaceOrderOptions struct { Timeout time.Duration; Priority int },避免方法签名爆炸 - error 处理:不要只返回
error,建议搭配自定义 error 类型(var ErrOrderFailed = errors.New("order failed")),方便上层 switch 判断
测试 Facade 时最容易漏掉的三件事
Facade 的测试重点不是逻辑分支,而是「是否正确编排了子系统调用」。Mock 很容易写过头,或者漏掉关键路径。
立即学习“go语言免费学习笔记(深入)”;
- 忘测失败回滚:比如支付成功但库存扣减失败,
PlaceOrder是否调用了payment.Refund()?这个流程必须在测试里显式触发 - 忽略并发安全:如果 Facade 内部缓存了某个 client 实例(如
*http.Client),要确认它本身是并发安全的;Go 标准库多数 client 是,但第三方库未必 - 没验证参数传递:Mock 子系统时,只检查「有没有被调用」不够,还得断言传入的
userID和itemID是否和 Facade 入参一致——否则可能是硬编码值蒙混过关










