外观模式的核心作用是为耦合的子系统提供统一简洁的高层接口。它不改变底层逻辑,仅封装初始化与调用流程,如compilerfacade将lexer、parser、semanticchecker的固定调用序列整合为单个compile()方法。

外观模式的核心作用是什么
外观模式不是为了炫技,而是当系统里有一堆类、接口、初始化流程互相耦合时,给调用方一个干净的入口。它不改变底层逻辑,只做一层“门面”封装——比如你不用再手动创建 SubsystemA、SubsystemB、调用它们各自的 init() 和 process(),而是交给一个 Facade 类统一协调。
关键判断:如果你发现每次使用某组功能都要写 5 行以上初始化+调用代码,且这些步骤几乎不变,那就适合加个外观。
怎么写一个最小可行的 C++ 外观类
用一个编译器前端的小例子说明:假设你要把源码转成 AST,背后涉及词法分析器(Lexer)、语法分析器(Parser)、语义检查器(SemanticChecker)三个子系统。
外观类本身不继承也不模板化,就是普通聚合:
class CompilerFacade {
private:
Lexer lexer_;
Parser parser_;
SemanticChecker checker_;
public:
CompilerFacade() = default;
// 一行触发整套流程
std::shared_ptr<astnode> compile(const std::string& source) {
auto tokens = lexer_.tokenize(source);
auto ast = parser_.parse(tokens);
checker_.validate(ast);
return ast;
}
};</astnode>
注意点:
-
CompilerFacade持有具体子系统对象(非指针也可,除非需要延迟构造或共享) - 所有子系统方法调用顺序、参数传递、错误处理都封装在
compile()内部,外部看不到 - 如果某个子系统可能失败(比如
parse()抛异常),外观类要决定是吞掉、转换、还是透传——通常建议转换为统一错误类型,如CompilationError
什么时候不该用外观模式
外观不是万能胶,硬贴反而增加维护成本。
- 子系统接口本身就很稳定、调用路径单一(比如就一个函数
encrypt(data, key)),加外观纯属多一层间接 - 不同业务场景需要定制子系统调用顺序或跳过某步(例如测试时想绕过语义检查),此时外观的“固定流程”反而碍事
- 子系统之间存在强生命周期依赖,而外观类无法控制析构顺序(比如
Parser构造依赖Lexer的内部缓冲区),这时需谨慎管理成员声明顺序或改用智能指针 + 显式初始化
和工厂模式、适配器模式容易混淆的点
外观关注「简化已有接口的组合调用」;工厂关注「如何创建对象」;适配器关注「让不兼容接口能一起工作」。
- 如果你在外观类里写了
new Lexer()或std::make_unique<parser>()</parser>,那只是实现细节,不是工厂——除非你把创建逻辑抽成虚函数并允许子类替换,才算引入工厂 - 如果
Facade里要包装一个 C 风格的parse_c_style(const char*, size_t)接口,使其能接受std::string,那里面就得加一层适配逻辑,但这是外观内部的实现手段,不改变外观的本质职责
runEverythingWithConfig(Config cfg),结果发现测试困难、难以复用。更务实的做法是按场景拆小接口,比如 parseOnly()、checkOnly(std::shared_ptr<astnode>)</astnode>,再在顶层组合。这看起来像多了几个函数,实则提升了可测性和演进弹性。C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











