外观模式:统一接口、简化子系统

外观模式,说白了就是给你的系统装一个「前台接待」。

我刚开始做架构时,经常遇到这样的场景:一个业务操作需要调用三四个子系统,每个子系统又有自己的初始化顺序、参数校验、异常处理。调用方苦不堪言,代码里全是「先调A,再调B,如果B失败要回滚A...」这种逻辑。

嗯,外观模式就是来解决这个问题的。

什么是外观模式?

外观模式(Facade Pattern)属于结构型设计模式。它为一组复杂的子系统接口提供一个统一的、更高级的接口。调用方只需要跟这个外观类打交道,不需要关心子系统内部怎么折腾。

我个人的理解:外观模式就是「封装变化,暴露稳定」。子系统可以随便改,只要外观接口不变,调用方就不用动代码。

角色 说明
外观类(Facade) 对外提供统一接口,内部协调子系统
子系统类(Subsystem) 实现具体功能,不感知外观类的存在
客户端(Client) 只调用外观类的方法

一个简单的例子

假设我们有一个订单系统,下单时需要做三件事:校验库存、扣减余额、发送通知。

没有外观模式时,客户端代码是这样的:

// 客户端直接操作子系统
InventoryService inventory = new InventoryService();
PaymentService payment = new PaymentService();
NotificationService notification = new NotificationService();

if (inventory.checkStock("item123", 1)) {
    payment.deduct("user456", 99.0);
    notification.send("user456", "下单成功");
}

看着还行?那如果再加一个「积分赠送」呢?再加一个「物流预分配」呢?调用方代码会越来越臃肿。

用外观模式改造一下:

// 外观类
public class OrderFacade {
    private InventoryService inventory;
    private PaymentService payment;
    private NotificationService notification;

    public OrderFacade() {
        this.inventory = new InventoryService();
        this.payment = new PaymentService();
        this.notification = new NotificationService();
    }

    public void placeOrder(String itemId, String userId, double amount) {
        if (!inventory.checkStock(itemId, 1)) {
            throw new RuntimeException("库存不足");
        }
        payment.deduct(userId, amount);
        notification.send(userId, "下单成功");
    }
}

// 客户端代码
OrderFacade facade = new OrderFacade();
facade.placeOrder("item123", "user456", 99.0);

你看,客户端只需要一行调用。子系统再怎么变,客户端都不用改。

我的习惯:外观类里不要写业务逻辑。它只负责编排,不负责决策。判断、计算这些事交给子系统去做。

实战:API网关设计

外观模式在企业级应用中最典型的场景就是API网关。我在项目中遇到过好几次这样的需求:微服务多了之后,客户端要调用好几个接口才能完成一个功能,而且每个接口的认证、限流、日志都得单独处理。

API网关本质上就是一个外观模式——它把多个微服务的接口统一暴露给客户端,同时把认证、限流、日志这些横切关注点集中处理。

下面是一个简化版的API网关设计:

// 网关外观类
public class ApiGatewayFacade {
    private AuthService authService;
    private RateLimiter rateLimiter;
    private Logger logger;
    private UserService userService;
    private OrderService orderService;

    public ApiGatewayFacade() {
        this.authService = new AuthService();
        this.rateLimiter = new RateLimiter();
        this.logger = new Logger();
        this.userService = new UserService();
        this.orderService = new OrderService();
    }

    public String handleRequest(String token, String apiName, Map<String, Object> params) {
        // 1. 认证
        if (!authService.authenticate(token)) {
            return "401 Unauthorized";
        }

        // 2. 限流
        if (!rateLimiter.allowRequest(token)) {
            return "429 Too Many Requests";
        }

        // 3. 路由到具体服务
        String result;
        switch (apiName) {
            case "getUserInfo":
                result = userService.getUserInfo((String) params.get("userId"));
                break;
            case "createOrder":
                result = orderService.createOrder(params);
                break;
            default:
                result = "404 Not Found";
        }

        // 4. 日志记录
        logger.log(token, apiName, result);

        return result;
    }
}
注意:网关层不要做太重的业务处理。我曾经见过一个网关里写了上千行的业务逻辑,结果每次改需求都要重启网关,所有服务跟着遭殃。网关只做路由和横切关注点,业务逻辑下沉到微服务里。

外观模式的核心逻辑

为了让你更直观地理解外观模式在API网关中的运作方式,我画了一张图:

客户端 请求 API网关 (外观类) 认证 · 限流 · 路由 用户服务 订单服务 支付服务 DB DB DB

这张图很清楚地展示了:客户端只跟API网关交互,网关内部协调多个微服务。每个微服务只关心自己的事,网关负责「穿针引线」。

外观模式的优缺点

优点很明显:

  • 简化调用:客户端不需要知道子系统内部细节
  • 降低耦合:客户端和子系统之间没有直接依赖
  • 集中控制:认证、限流、日志等横切关注点统一管理

缺点也要心里有数:

  • 外观类可能变成上帝类:如果什么逻辑都往里塞,外观类会变得臃肿不堪
  • 不符合开闭原则:新增子系统可能需要修改外观类
避坑指南:我曾经在一个项目里把网关层做成了「万能中转站」,所有请求都经过它,结果网关成了性能瓶颈。后来我们做了拆分——读请求走缓存网关,写请求走业务网关,才把问题解决。外观模式不是银弹,该拆的时候别犹豫。

什么时候用外观模式?

我个人总结了三类场景:

  1. 子系统太多太杂:客户端需要跟多个子系统交互,代码里全是编排逻辑
  2. 需要统一入口:比如API网关、SDK封装、第三方服务对接
  3. 子系统需要分层:每一层对外暴露一个外观类,内部细节对外隐藏

你想想看,如果你的代码里出现了「先调A,再调B,然后调C,如果C失败要回滚A和B」这种逻辑,而且出现在多个地方——嗯,这就是外观模式该出场的时候了。

我的建议:外观模式不要滥用。如果一个子系统只有两三个类,调用方直接使用也没什么问题。过度封装反而增加了不必要的抽象层。记住一句话:外观模式是为了简化,不是为了炫技。

外观模式的核心思想其实很简单——给复杂系统装一个「前台」。调用方不用知道后台有多少部门在协作,只需要跟前台说一声「我要办这个事」就行了。API网关就是这种思想在企业级架构中的最佳实践。


公众号:蓝海资料掘金营,微信deep3321