当某操作需≥3个无强聚合关系的组件协作时,应使用Facade;它通过显式依赖注入、错误透传和顺序控制封装组合调用,避免重复代码与隐式耦合。

Go 里什么时候该用 Facade 而不是直接调用子系统?
外观模式在 Go 中不是语言特性,也没有标准库 facade 包——它是一种组织方式。当你发现业务逻辑里反复出现「要同时初始化 A、B、C,再按顺序调 a.Do() → b.Process() → c.Commit()」,那就到了该抽 Facade 的时候。
常见错误现象:
- 同一套组合调用在多个 handler 或 test 文件里 copy-paste
- 修改
B的参数后,十几个地方漏改,测试全挂 - 新人看代码时卡在「这个
config.NewXClient()和cache.NewYStore()为什么总是一起出现?」
使用场景很实际:微服务间协调、CLI 工具的命令执行链、Web 请求中「鉴权 + 日志 + DB 写入 + 消息推送」这一整条路。
判断依据很简单:如果某个操作需要 ≥3 个独立组件协作,且它们之间没有强聚合关系(比如不是同一个 struct 的字段),就值得包一层 Facade。
立即学习“go语言免费学习笔记(深入)”;
怎么写一个不假、不重、不难测的 Facade 结构?
Go 不靠继承,Facade 就是普通 struct + 方法。关键不在“模式”,而在“依赖怎么进、错误怎么出”。
- 用字段显式持有依赖,而不是在方法里 new 出来(否则无法 mock、无法复用已有实例)
- 构造函数接收所有依赖,不隐藏初始化逻辑(避免
facade.New()内部悄悄 newredis.Client) - 方法返回 error 优先于 panic,且 error 应包含上下文(比如 “failed to commit to db:
sql.ErrNoRows” 而非泛泛的 “operation failed”)
type OrderFacade struct {
payment *PaymentService
inventory *InventoryClient
notifier *SmsNotifier
}
<p>func NewOrderFacade(p <em>PaymentService, i </em>InventoryClient, n <em>SmsNotifier) </em>OrderFacade {
return &OrderFacade{payment: p, inventory: i, notifier: n}
}</p><p>func (f *OrderFacade) PlaceOrder(req OrderReq) error {
if err := f.payment.Charge(req.Card, req.Amount); err != nil {
return fmt.Errorf("payment failed: %w", err)
}
if err := f.inventory.Reserve(req.Items); err != nil {
return fmt.Errorf("inventory reserve failed: %w", err)
}
f.notifier.Send(fmt.Sprintf("Order %s placed", req.ID))
return nil
}
Facade 的参数设计:传结构体还是传单个值?
取决于变化频率和组合粒度。
- 如果子系统方法参数经常变(比如
cache.Get(key, ttl, tags...)),别把所有参数都塞进Facade.PlaceOrder();改用选项函数或配置结构体 - 如果只是简单转发(如
facade.GetUser(id)→db.FindUserByID(id)),直接传参更直白,别为了“统一”硬套结构体
容易踩的坑:
- 把
Facade变成万能参数兜底结构体,里面塞 12 个字段,8 个可选,结果没人记得哪个必填 - 在
Facade方法里做参数校验,但校验规则和下游子系统不一致,导致错误提示错位(比如 facade 检了长度,db 层又检了格式)
性能影响很小,但要注意:
- 所有依赖都是指针传入,避免大结构体拷贝
- 不在
Facade里做同步阻塞操作(如磁盘读、HTTP 调用),除非你明确这就是它的职责边界
测试 Facade 时最容易漏掉什么?
不是测它“能不能跑通”,而是测它“会不会掩盖错误”“会不会漏传依赖”。
- 必须测 error 路径:比如
payment.Charge返回ErrInsufficientFunds,Facade.PlaceOrder是否原样包装并透出,而不是吞掉或转成fmt.Errorf("something went wrong") - 测依赖调用顺序:用 mock 验证是不是先调了
payment.Charge,再调inventory.Reserve,不能反了 - 测空依赖 panic:把
notifier设为nil后调PlaceOrder,是否 panic?应该在构造函数里检查,而不是等到发送短信时才崩
可给出的最小验证点:
-
NewOrderFacade(nil, inv, noti)应 panic 或返回 error(构造期检查) -
facade.PlaceOrder(validReq)调用了payment.Charge一次、inventory.Reserve一次、notifier.Send一次 -
facade.PlaceOrder(reqWithInvalidCard)返回的 error 包含payment原始 error,且不调用后续步骤
Facade 本身没魔法,复杂点永远在依赖之间的协议对齐——比如 inventory.Reserve 失败时要不要自动回滚 payment.Charge?这已经超出外观模式范畴,得看业务契约。别指望包一层就自动解决分布式一致性。










