外观模式在Go中通过组合实现:定义结构体嵌入接口类型依赖,构造函数注入依赖并返回错误,支持延迟初始化与测试mock;适用于调用链深、模块耦合紧、测试依赖多的场景。

外观模式在Go里不是靠继承,而是靠组合
Go没有类和继承,所以没法像Java那样定义一个 Facade 类去继承一堆子系统。实际做法是定义一个结构体,把需要封装的依赖(比如数据库连接、HTTP客户端、缓存实例)作为字段嵌入,再提供简洁的方法对外暴露。关键在于:这些字段通常是接口类型,便于测试和替换。
- 不要直接持有具体实现(如
*sql.DB),优先用接口(如database/sql包里的driver.Conn或自定义DataStore接口) - 构造函数应接受依赖项,而不是在内部 new,否则无法控制生命周期和 mock
- 如果子系统初始化成本高(如建立连接池),外观结构体本身可带
Init()方法延迟加载
什么时候该用外观模式?看这三种典型场景
不是所有封装都叫外观模式。真正适用的情况有明确信号:
- 调用链超过3层且频繁出现,比如每次处理订单都要手动查用户→查库存→调支付→写日志→发消息
- 多个包/模块边界模糊,外部代码不得不 import 七八个内部包才能完成一个业务动作
- 测试时要 mock 五六个依赖才跑得通单元测试,说明职责没聚拢
反例:只把一个函数简单包装成另一个函数(如 func GetUser(id int) { return db.GetUser(id) }),这不是外观,只是别名。
NewOrderService 这类构造函数容易漏掉错误处理
外观结构体的初始化函数常被写成无返回错误的版本,但实际中依赖可能失败——比如 Redis 连接超时、配置缺失、证书校验失败。不暴露错误会导致 panic 或静默降级。
启科网络商城系统由启科网络技术开发团队完全自主开发,使用国内最流行高效的PHP程序语言,并用小巧的MySql作为数据库服务器,并且使用Smarty引擎来分离网站程序与前端设计代码,让建立的网站可以自由制作个性化的页面。 系统使用标签作为数据调用格式,网站前台开发人员只要简单学习系统标签功能和使用方法,将标签设置在制作的HTML模板中进行对网站数据、内容、信息等的调用,即可建设出美观、个性的网站。
- 构造函数应返回
(*OrderService, error),而不是*OrderService - 避免在构造函数里执行阻塞操作(如
http.Get),除非明确设计为“启动即就绪” - 如果必须做预检,用
Validate() error方法分离校验逻辑,让使用者决定何时调用
func NewOrderService(cfg Config, db DB, cache Cache) (*OrderService, error) {
if cfg.Timeout <= 0 {
return nil, errors.New("invalid timeout")
}
if db == nil {
return nil, errors.New("db is required")
}
return &OrderService{cfg: cfg, db: db, cache: cache}, nil
}
外观不该承担业务规则,否则会变重
外观的核心价值是“减少调用方的认知负担”,不是“集中业务逻辑”。一旦开始在 OrderService.Process() 里写 if-else 判断优惠券类型、计算满减、触发风控,它就不再是外观,而成了协调器甚至服务层。
- 外观方法体内应只做参数转换、顺序编排、错误聚合,不写分支逻辑
- 复杂判断交给独立的
Calculator、Validator等小接口,外观只负责传参和收结果 - 如果发现外观方法超过20行、含 for 或 switch,大概率该拆了
外观越薄,越容易被复用;越厚,越难测试和演进。很多人卡在这一步,把外观写成“上帝对象”,最后谁都不敢动。









