外观模式本质是删减接口而非叠加抽象,应仅暴露2–3个必要函数,用pimpl隐藏实现、避免头文件依赖,资源管理优先值语义与智能指针,禁用裸指针和上帝类。

外观模式不是加壳,是删掉用户不该看到的细节
外观模式在 C++ 里根本不是为了“包装一堆类”,而是当你发现调用方总要写重复的初始化顺序、总要手动管理 std::shared_ptr 生命周期、总要在三四个对象间传同一个配置参数时,就该把它提出来——它本质是**删减接口**,不是叠加抽象。
常见错误是把所有子系统类全塞进一个 Facade 类里,结果 Facade 自己变成上帝类,还带一堆 #include 依赖。真正该做的,是只暴露调用方真正需要的那 2–3 个函数,其余全藏在实现文件里(.cpp),头文件里连子系统类名都不出现。
- 用
pimpl惯用法隐藏子系统类型:头文件只声明class Facade;,具体成员全在.cpp里用std::unique_ptr<Impl>持有 - 构造函数不接受原始指针,只接受可移动的资源(如
std::string config_path或std::shared_ptr<Logger>),避免调用方误传裸指针引发生命周期问题 - 所有对外函数返回值尽量是值语义(
std::vector<Result>、std::optional<Data>),而非引用或指针——别让用户猜谁负责释放
std::shared_ptr 和 std::unique_ptr 在 Facade 里怎么选
选哪个不看“共享”还是“独占”,而看调用方是否需要多处持有同一份后端资源。比如日志器、配置管理器这类单例倾向的组件,用 std::shared_ptr 合理;但像一次性的解析器、临时的缓存上下文,必须用 std::unique_ptr,否则容易在多线程下触发隐式拷贝和引用计数竞争。
典型坑是:在 Facade 构造函数里接收 std::shared_ptr<Subsystem>,但内部又用 new Subsystem 手动 new 出来再包成 shared_ptr——这既绕过 RAII,又让调用方无法控制销毁时机。
立即学习“C++免费学习笔记(深入)”;
- 如果 Facade 内部创建子系统,一律用
std::make_unique或std::make_shared,绝不裸new - 如果 Facade 接收外部传入的资源,参数类型优先用
const std::shared_ptr<T>&(避免拷贝)或std::unique_ptr<T>(明确转移所有权) - 返回子系统实例?别干这事。返回
void或状态码就够了。真要暴露能力,加一层窄接口(比如Facade::get_reader()返回封装过的ReaderHandle,而非原始std::shared_ptr<Reader>)
编译依赖爆炸?头文件里禁止 #include 子系统头
只要 Facade.h 里 #include "NetworkClient.h" 或 #include "DatabaseConnection.h",调用方一包含这个头,就得被迫编译整个网络栈或数据库层——这不是简化,是埋雷。
正确做法是前向声明 + pimpl。头文件里只写 class NetworkClient;、class DatabaseConnection;,所有具体类型、函数定义、sizeof 相关操作全挪到 Facade.cpp 里。
- 错误示范:
std::shared_ptr<NetworkClient> client_;出现在头文件 → 编译器需要知道NetworkClient完整定义 → 必须 include - 正确写法:
struct Impl;+std::unique_ptr<Impl> pimpl_;,Impl定义和成员变量全在 .cpp 里 - 如果必须导出某个子系统类型(比如返回自定义 error 类型),用
class FacadeError;前向声明,定义放在单独的facade_error.h,且不依赖任何子系统头
别让 Facade 成为性能瓶颈点
外观模式常被当成“安全网”,结果所有调用都经由 Facade 中转,哪怕只是读一个配置字段也要走一遍锁、日志、参数校验——这比直接调用慢 3–5 倍。关键判断标准是:这个操作是否天然需要协调多个子系统?如果不是,就别塞进去。
比如 Facade::get_user_name(int id),背后其实只查内存缓存,却硬要走一遍数据库连接池检查、SQL 注入过滤、审计日志记录……这就是滥用。
- 高频路径(如每帧调用)函数,应在 Facade 内做零开销抽象:内联、无锁、无虚函数调用
- 所有日志、监控、鉴权逻辑,用策略类注入(如
std::function<void(const std::string&)> audit_log),默认为空函数,不开销 - 调试时加断点发现
Facade::do_something()调用了 7 层函数才到实际干活的代码?说明抽象层级错了,得拆或删
最麻烦的从来不是写 Facade,而是判断哪段逻辑真该被它管——边界模糊时,宁可先不加,等出现三个以上重复调用链再抽。











