Go中外观模式用结构体聚合接口依赖实现,NewOrderFacade传入所有必需服务,方法透传错误并保留因果链,禁止硬编码子系统逻辑或延迟初始化。

Go 语言没有类和继承,所以外观模式(Facade Pattern)不能照搬面向对象教科书里的写法;它本质是「用一个统一接口封装一组复杂子系统」,在 Go 里直接用结构体 + 方法组合就能干净实现,关键在于职责隔离和导出控制。
用 struct 封装多个子服务实例
外观的核心是聚合子系统并提供简化入口。不要把子系统逻辑塞进外观方法里,而是通过字段持有它们的实例——这样便于测试替换、依赖注入,也符合 Go 的组合哲学。
常见错误:把所有子系统逻辑硬编码在外观方法内部,导致无法单独测试或 mock。
- 子系统类型应定义为接口(如
UserService、NotificationService),外观只依赖接口 - 外观结构体字段用小写首字母(如
userService),避免意外导出 - 构造函数(如
NewOrderFacade)负责组装,调用方只关心这个工厂函数
NewOrderFacade 必须接收所有依赖,禁止延迟初始化
外观不是单例工具类,它的行为强依赖底层服务状态。如果在方法里才去 new 子服务,会导致不可测、难替换、资源泄漏(比如重复开数据库连接)。
立即学习“go语言免费学习笔记(深入)”;
典型反模式:func (f *OrderFacade) PlaceOrder(...) { db := sql.Open(...) } —— 每次都新建连接,且无法注入 mock DB。
- 所有依赖必须在
NewOrderFacade(userSvc UserService, notifySvc NotificationService, ...)中传入 - 若某子系统可选,用指针或
nil判断,但需在文档中明确其影响(例如通知失败不阻断下单) - 构造函数应做最小必要校验(如
if userSvc == nil { panic("userSvc required") }),不建议静默容忍 nil
外观方法要返回具体错误,别吞掉子系统错误
外观不是错误过滤器。用户需要知道是库存不足、支付超时,还是用户不存在——这些信息来自不同子系统,外观只需透传或包装,不能统一转成 "operation failed"。
容易踩的坑:用 fmt.Errorf("failed to place order") 抹掉原始错误链,导致线上排查困难。
- 用
fmt.Errorf("place order: %w", err)保留错误因果链 - 对特定错误做语义化包装(如将
sql.ErrNoRows转为UserNotFound),但保留原始 error 作为 cause - 避免在外观层重试(如自动重试支付),那是子系统或业务策略层的事
type OrderFacade struct {
userService UserService
paymentService PaymentService
inventoryService InventoryService
}
func NewOrderFacade(u UserService, p PaymentService, i InventoryService) *OrderFacade {
return &OrderFacade{
userService: u,
paymentService: p,
inventoryService: i,
}
}
func (f *OrderFacade) PlaceOrder(ctx context.Context, req PlaceOrderRequest) error {
if _, err := f.userService.GetUser(ctx, req.UserID); err != nil {
return fmt.Errorf("get user: %w", err)
}
if err := f.inventoryService.Reserve(ctx, req.Items); err != nil {
return fmt.Errorf("reserve inventory: %w", err)
}
if err := f.paymentService.Charge(ctx, req.Payment); err != nil {
return fmt.Errorf("charge payment: %w", err)
}
return nil
}
真正难的是边界划分:哪些逻辑该放进子系统,哪些留在外观?比如「库存扣减失败时是否自动回滚用户账户余额」——这已经超出外观职责,属于事务协调层。外观只管顺序调用+错误传播,别越界。










