外观模式适合解决子系统耦合严重、调用流程复杂易错的问题,通过提供统一入口封装初始化顺序和方法调用组合。

外观模式适合解决什么问题
当你面对一堆耦合严重的子系统(比如订单、支付、库存、物流模块),每次调用都要手动初始化七八个对象、按顺序调用十几个方法,而且不同业务场景组合方式还不一样——这时候外观模式不是“炫技”,是止痛药。
它不改变底层逻辑,只提供一个统一入口,把「谁该先初始化」「哪几个方法必须连着调」这些容易出错的流程封装起来。
怎么写一个靠谱的 OrderFacade
别一上来就抽象接口、搞泛型工厂。先从最痛的业务流入手,比如「用户下单并支付成功」:
-
OrderFacade应该是具体类,不是接口——初期没那么多扩展需求,接口反而增加调用方理解成本 - 构造函数里只接收真正需要协作的子系统实例,比如
IOrderService、IPaymentGateway、IInventoryClient,别把日志、配置、缓存管理器全塞进来 - 方法命名要反映业务动作,不是技术动作:
PlaceAndPayOrder()比ExecuteWorkflow()清晰得多 - 所有异常必须重新包装:子系统抛
SqlException或HttpRequestException,外观层统一转成OrderProcessingException,否则上层根本没法做有意义的错误处理
public class OrderFacade
{
private readonly IOrderService _orderService;
private readonly IPaymentGateway _payment;
private readonly IInventoryClient _inventory;
public OrderFacade(IOrderService orderService, IPaymentGateway payment, IInventoryClient inventory)
{
_orderService = orderService;
_payment = payment;
_inventory = inventory;
}
public async Task<orderresult> PlaceAndPayOrder(OrderRequest request)
{
try
{
var orderId = await _orderService.Create(request);
await _inventory.ReserveStock(request.Items);
await _payment.Charge(request.PaymentInfo);
await _orderService.MarkAsPaid(orderId);
return new OrderResult { Success = true, OrderId = orderId };
}
catch (InventoryException ex)
{
throw new OrderProcessingException("库存不足", ex);
}
catch (PaymentRejectedException ex)
{
throw new OrderProcessingException("支付失败", ex);
}
}
}</orderresult>
什么时候不该用外观模式
外观模式是胶水,不是万能胶。以下情况硬套反而更乱:
- 子系统之间本来就没耦合,比如三个完全独立的工具类(
FileHelper、JsonSerializer、TimeConverter)——直接调用更直白 - 业务流程本身就不稳定,每周都在改步骤或增删环节(比如风控规则频繁迭代)——外观类会变成高频修改点,违背「稳定接口」初衷
- 团队里一半人不知道外观类存在,还在各处重复写一样的初始化+调用链——说明文档和命名没到位,不是模式的问题,是落地的问题
DI 容器里注册外观类的坑
很多人在 Program.cs 里这么写:
services.AddSingleton<orderfacade>();</orderfacade>
结果运行时报 InvalidOperationException: Unable to resolve service for type 'IOrderService'——因为外观类的构造函数参数没被容器识别。
正确做法是显式传递依赖:
services.AddSingleton<orderfacade>(sp => new OrderFacade(
sp.GetRequiredService<iorderservice>(),
sp.GetRequiredService<ipaymentgateway>(),
sp.GetRequiredService<iinventoryclient>()));</iinventoryclient></ipaymentgateway></iorderservice></orderfacade>
或者更推荐:保持构造函数简单,让 DI 自动解析,前提是所有依赖都已注册且生命周期匹配(比如别把 Scoped 服务注入到 Singleton 外观类里)。
外观类本身通常用 Scoped 就够了,除非它真有跨请求共享状态的需求——这种需求本身就该警惕。










