调个订单要写 20 行代码?外观模式说你根本不需要知道底层有多乱

调个订单要写 20 行代码?外观模式说你根本不需要知道底层有多乱

写过电商或者票务系统的,下面这种代码应该不陌生:

public OrderResult createOrder(CreateOrderRequest req) {
    // 1. 查航班缓存
    FlightCache flightCache = cacheManager.getFlight(req.getFlightKey());
    if (flightCache == null) {
        flightCache = flightService.queryFromDB(req.getFlightId());
        cacheManager.putFlight(req.getFlightKey(), flightCache);
    }

    // 2. 验价
    PriceVerifyResult verifyResult = priceService.verify(
        flightCache, req.getCabinCode(), req.getPrice()
    );
    if (!verifyResult.isMatch()) {
        return OrderResult.priceMismatch();
    }

    // 3. 锁座
    SeatLockResult lockResult = seatService.lock(
        req.getFlightId(), req.getCabinCode(), req.getPassengerCount()
    );
    if (!lockResult.isSuccess()) {
        return OrderResult.seatUnavailable();
    }

    // 4. 创建订单
    Order order = orderService.createOrder(
        flightCache, verifyResult, lockResult, req
    );

    // 5. 发消息
    messageService.sendOrderCreated(order.getId());

    // 6. 记日志
    auditLogService.logOrderCreate(order.getId(), req);

    return OrderResult.success(order);
}

6 个步骤,20 多行代码,调用方需要知道缓存怎么查、价格怎么验、座位怎么锁。问题是这种"下单"的逻辑在 App 端、H5 端、小程序端各出现了一次,三份代码各自维护,改一个漏两个。

外观模式就是干这个的——把一堆复杂的子系统调用,包成一个简单的方法。


核心思路

不引入新的抽象,不加新的接口,就是把已有的子系统调用封装起来,让调用方只和"门面"打交道。

public class OrderFacade {
    private final CacheManager cacheManager;
    private final FlightService flightService;
    private final PriceService priceService;
    private final SeatService seatService;
    private final OrderService orderService;
    private final MessageService messageService;
    private final AuditLogService auditLogService;

    // 构造器注入省略

    public OrderResult createOrder(CreateOrderRequest req) {
        FlightCache flightCache = resolveFlight(req);
        PriceVerifyResult verifyResult = verifyPrice(flightCache, req);
        if (!verifyResult.isMatch()) {
            return OrderResult.priceMismatch();
        }

        SeatLockResult lockResult = lockSeat(req);
        if (!lockResult.isSuccess()) {
            return OrderResult.seatUnavailable();
        }

        Order order = orderService.createOrder(flightCache, verifyResult, lockResult, req);
        messageService.sendOrderCreated(order.getId());
        auditLogService.logOrderCreate(order.getId(), req);

        return OrderResult.success(order);
    }

    private FlightCache resolveFlight(CreateOrderRequest req) {
        FlightCache cache = cacheManager.getFlight(req.getFlightKey());
        if (cache == null) {
            cache = flightService.queryFromDB(req.getFlightId());
            cacheManager.putFlight(req.getFlightKey(), cache);
        }
        return cache;
    }

    private PriceVerifyResult verifyPrice(FlightCache cache, CreateOrderRequest req) {
        return priceService.verify(cache, req.getCabinCode(), req.getPrice());
    }

    private SeatLockResult lockSeat(CreateOrderRequest req) {
        return seatService.lock(req.getFlightId(), req.getCabinCode(), req.getPassengerCount());
    }
}

调用方变成:

@Autowired
private OrderFacade orderFacade;

OrderResult result = orderFacade.createOrder(request);

一行搞定。不管底层有多少个子系统,调用方都不需要知道。


你其实一直在用外观模式

  • JdbcTemplate:JDBC 那一堆 Connection、PreparedStatement、ResultSet 的操作全给你包了,你只需要调 query()

  • RestTemplate / RestClient:HTTP 连接、序列化、异常处理全封装了

  • SLF4J:底层是 Logback 还是 Log4j2 你不用管,调 info() 就行

  • Spring 的 @Transactional:开启事务、提交、回滚全给你包了

这些你天天在用的东西,底层就是外观模式。你从来没关心过 JdbcTemplate 内部怎么拿连接、怎么释放、怎么处理异常,因为那些脏活都被包在"门面"里了。


外观模式和适配器模式的区别

这俩容易搞混:

  • 适配器是让两个不兼容的接口能对话,重点在"转接"

  • 外观是让一堆复杂的调用变简单,重点在"简化"

适配器解决的是"形状不对"的问题,外观解决的是"太复杂"的问题。

你有一个老系统接口要对接新系统,用适配器。你有十个子系统要一起调,用外观。同一个项目里两个都用也很正常。


容易踩的坑

外观类容易变成上帝类。一开始只是包了一下下单流程,后来有人觉得"取消订单也可以放进来",再后来"查询订单也放进来",最后这个 Facade 有 40 个方法,比原来的 Service 还乱。

解法很简单:按业务场景拆 FacadeOrderCreateFacadeOrderCancelFacadeRefundFacade,每个只管一件事。别让一个门面变成整个系统的入口。

另一个坑:外观类不应该加业务逻辑。它只负责编排,不做决策。验价的规则在 PriceService 里,不在 Facade 里。Facade 里出现 if-else 业务分支,就说明职责越界了。


做设计模式小程序「爪爪代码冒险记」的时候,外观模式那关画的是卡皮巴拉面对一堆乱七八糟的线缆,然后一头全插到一个接线板上——思路完全一样。微信搜搜看。

聊天