门面模式应在多次调用需重复初始化、参数组装和异常处理时引入;当胶水代码已耦合多个子系统(如db、cache、validator)且测试/替换成本高时,抽取为facade类收益最大。

门面模式不是为了“设计漂亮”,而是当你发现每次调用都要写五六行初始化、参数组装、异常兜底时,它才真正值得加。
什么时候该加 Facade 而不是直接封装函数?
你已经在写类似这样的胶水代码:
db = DatabaseConnection(host=cfg.db_host, port=cfg.db_port)
cache = RedisClient(url=cfg.redis_url, decode_responses=True)
validator = DataValidator(schema=SCHEMA_USER)
result = validator.validate(data)
if result.is_valid:
db.insert("users", result.cleaned_data)
cache.set(f"user:{result.id}", result.cleaned_data, ex=3600)
——这已经是在手动实现门面了,只是没抽成类。此时加 Facade 的收益最明显:
- 多个子系统(
DatabaseConnection、RedisClient、DataValidator)耦合在业务逻辑里 - 测试时要 mock 三四个对象,setup 成本高
- 某天要换掉
RedisClient改用MemcachedClient,得改所有调用点
Facade 类该暴露什么方法?
只暴露业务语义明确的原子操作,不暴露底层细节。比如:
- ✅ 推荐:
create_user(self, raw_data: dict) → User(返回领域对象,不是原始 dict) - ❌ 避免:
insert_to_db_and_cache(self, table: str, key: str, value: dict, ttl: int)(泄露存储细节) - ❌ 避免:
get_validator_and_run(self, schema_name: str)(把子系统创建逻辑暴露出去)
关键判断:如果某个方法名让你需要解释“为什么要传这个参数”,那它大概率不该出现在 Facade 上。
如何避免 Facade 变成上帝对象?
门面失控的典型信号是:它的构造函数开始接受七八个依赖,或者方法里出现 if subsystem == "payment" 分支。应对方式很实在:
- 按业务边界拆,而不是按技术分层:比如
UserFacade和PaymentFacade是合理的;DBFacade和CacheFacade是错的 - 依赖通过构造函数注入,但只注入它真正用到的实例,不要传一个大配置字典再自己拆
- 拒绝在
Facade里做重试、熔断、日志埋点——这些交给中间件或装饰器,保持门面纯逻辑
示例中 UserFacade.__init__ 接收 db: DatabaseConnection、cache: RedisClient、validator: DataValidator 三个具体对象,不多不少。
为什么不用模块级函数替代 Facade 类?
函数看似更轻量,但会快速滑向两个坑:
- 状态管理困难:比如缓存客户端需要复用连接池,函数每次调用都新建
RedisClient,连接数暴增 - 依赖难以替换:测试时想 mock 数据库行为,函数内部硬编码了
DatabaseConnection(...),只能 patch 模块,脆弱且难读 - 无法自然持有上下文:例如用户操作需要绑定请求 ID 做全链路追踪,类可以存为实例属性,函数就得靠线程局部变量或显式传参
所以,只要涉及多个协作对象 + 需要生命周期管理(如连接复用、资源清理),就用类;否则单行逻辑,函数更干净。
真正容易被忽略的是:门面的边界感。它不解决底层 bug,也不替代接口抽象,只负责让上层调用者忘掉“我正在同时跟数据库、缓存、校验器打交道”这件事——一旦你发现要在门面里处理子系统间的竞态条件或数据一致性协议,说明职责已经越界了。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











