当前位置: 首页 > news >正文

你定义的门面接口其实在用外观模式——但99%的人把它用成了垃圾堆

见过太多这种代码了:

@Service

public class OrderFacade {

@Autowired

private OrderDao orderDao;

@Autowired

private InventoryService inventoryService;

@Autowired

private PaymentService paymentService;

@Autowired

private LogisticsService logisticsService;

@Autowired

private CouponService couponService;

@Autowired

private NotificationService notificationService;

@Autowired

private RiskControlService riskControlService;

@Autowired

private AccountingService accountingService;

// 九个依赖,还觉得自己写得挺对

public OrderResult placeOrder(OrderRequest request) {

// 300 行代码揉在一起

// 校验、风控、库存、优惠券、支付、记账、物流、通知...

}

}

这不是门面模式。这叫把所有垃圾倒在一个桶里。

门面(Facade)本来想干嘛

外观模式的定义出奇地朴素:**为子系统中的一组接口提供一个统一的入口**。它不添加新功能,只是把复杂的内部细节藏起来,给外部一个简单的界面。

你打开电脑,按一下开机键,主板通电、BIOS 自检、操作系统加载、驱动初始化——所有这些对你来说就是一个按钮。这就是门面。

在代码里,最直观的例子是编译器的前端:

// 没有门面——调用方需要了解编译的每一步内部细节

Lexer lexer = new Lexer(sourceCode);

List tokens = lexer.tokenize();

Parser parser = new Parser(tokens);

AST ast = parser.parse();

SemanticAnalyzer analyzer = new SemanticAnalyzer(ast);

analyzer.analyze();

CodeGenerator generator = new CodeGenerator(ast);

Bytecode bytecode = generator.generate();

// 有了门面——调用方只需要关心输入和输出

Compiler compiler = new Compiler();

Bytecode bytecode = compiler.compile(sourceCode);

内部四步流程一个都不少,但调用者不需要知道 Lexer、Parser、SemanticAnalyzer 的存在。这就是门面做的事:**降低复杂度暴露面**。

Spring 里把门面用得最好的地方

JdbcTemplate 就是一个门面。JDBC 的原始操作有多繁琐不用多说了吧:

// 原始 JDBC —— 8 步操作,每个都得手动处理

Connection conn = null;

PreparedStatement stmt = null;

ResultSet rs = null;

try {

conn = dataSource.getConnection();

stmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?");

stmt.setLong(1, userId);

rs = stmt.executeQuery();

if (rs.next()) {

User user = new User();

user.setId(rs.getLong("id"));

user.setName(rs.getString("name"));

return user;

}

return null;

} catch (SQLException e) {

throw new RuntimeException(e);

} finally {

if (rs != null) try { rs.close(); } catch (SQLException ignored) {}

if (stmt != null) try { stmt.close(); } catch (SQLException ignored) {}

if (conn != null) try { conn.close(); } catch (SQLException ignored) {}

}

// JdbcTemplate —— 一行

User user = jdbcTemplate.queryForObject(

"SELECT * FROM users WHERE id = ?",

new BeanPropertyRowMapper<>(User.class),

userId

);

JdbcTemplate的门面之下,是DataSourceUtils管理连接、StatementCallback管理生命周期、SQLExceptionTranslator翻译异常——但使用者不需要知道这些。门面把复杂性封装在内部,暴露一个干净的接口。

另一个例子是 Spring MVC 的DispatcherServlet,它是整个 Web 层的门面:

- 请求来了 → DispatcherServlet

- HandlerMapping 找到哪个 Controller

- HandlerAdapter 调用它

- ViewResolver 渲染结果

这一整套流程对外部(Servlet 容器)只有一个入口:DispatcherServlet.service(request, response)

门面变垃圾堆的三个原因

**第一个原因:把门面当业务逻辑层。**

这是最常见的错误。很多人觉得「门面层」就是「把所有逻辑堆到一个类里」。门面应该组织调用,不应该包含具体的业务规则。

// 错误:门面里揉进了业务逻辑

public class OrderFacade {

public void cancelOrder(Long orderId) {

Order order = orderDao.findById(orderId);

// 这些是业务规则,不该在门面里

if (order.getStatus() == OrderStatus.SHIPPED) {

throw new BusinessException("已发货订单不可取消");

}

if (order.getCreateTime().plusHours(2).isAfter(Instant.now())) {

throw new BusinessException("下单超过2小时不可取消");

}

if (order.getAmount().compareTo(new BigDecimal("10000")) > 0) {

// 大额订单取消需要审批

approvalService.submit(orderId);

return;

}

orderDao.updateStatus(orderId, OrderStatus.CANCELLED);

inventoryService.restore(order.getItems());

paymentService.refund(orderId);

notificationService.notifyCancellation(orderId);

}

}

// 正确:门面只做调度,业务规则在下层

public class OrderFacade {

public void cancelOrder(Long orderId) {

// 规则下沉到领域服务

orderService.cancel(orderId); // 封装了所有业务规则

// 基础设施调度

inventoryService.restoreForOrder(orderId);

paymentService.refundForOrder(orderId);

notificationService.notify(OrderEvent.CANCELLED, orderId);

}

}

**第二个原因:把门面当上帝对象。**

一个门面引用 9 个依赖,处理 5 种不同类型的业务——拆分信号已经很明显了:

- 业务流程完全不同(下单 vs 退款 vs 查询)

- 每次改一个功能都要动同一个类

- 单元测试的 mock 对象比测试代码还多

这时候应该按业务流程拆门面:

public class OrderCreationFacade {

// 只管下单流程

}

public class OrderCancellationFacade {

// 只管取消流程

}

public class OrderQueryFacade {

// 只管查询

}

**第三个原因:把异常处理堆在门面里。**

public OrderResult placeOrder(OrderRequest req) {

try {

riskControlService.check(req.getUserId());

} catch (RiskRejectedException e) {

return OrderResult.reject("风控拒绝");

}

try {

inventoryService.lock(req.getItems());

} catch (InsufficientInventoryException e) {

return OrderResult.fail("库存不足");

}

try {

paymentService.charge(req.getUserId(), req.getAmount());

} catch (PaymentFailedException e) {

inventoryService.unlock(req.getItems()); // 回滚库存

return OrderResult.fail("支付失败");

}

// ...

}

门面不应该知道每个子系统的异常类型。正确的方式是统一异常处理:

public OrderResult placeOrder(OrderRequest req) {

try {

riskControlService.check(req.getUserId());

inventoryService.lock(req.getItems());

paymentService.charge(req.getUserId(), req.getAmount());

return OrderResult.success();

} catch (BusinessException e) {

compensationService.compensate(req); // 统一补偿

return OrderResult.fail(e.getMessage());

}

}

API 网关——门面模式的分布式版本

如果你在做微服务,Kong、Spring Cloud Gateway、Nginx 反代——这些都是门面模式在架构层面的体现。

客户端只跟网关打交道:

客户端 → 网关 → [用户服务, 订单服务, 库存服务, 支付服务, ...]

网关做的事跟代码层的门面一样:隐藏内部复杂性。客户端不需要知道后端有多少个服务,每个服务的地址是什么,它们之间怎么通信。认证、限流、日志、路由——全在网关层面解决。

但网关也有同样的陷阱:**不要把业务逻辑写进网关**。网关负责路由和横切关注点,不应该知道「取消订单前需要判断是否超过 2 小时」这种业务规则。

门面 vs 适配器 vs 中介者

这三个经常被搞混:

- **门面**:简化复杂子系统的接口。你主动设计了一个新接口,目的是让外部更容易使用。

- **适配器**:让不兼容的接口能一起工作。你被动地做了一个包装,因为两边已经存在且没法改。

- **中介者**:协调多个对象之间的交互。你不只是转发,你在管理对象之间的通信。

简单记:门面是一对多(一个入口,多个子系统),适配器是一对一(一个接口适配另一个),中介者是多对多(多个对象互相通信)。

门面最实用的自检问题

下次写一个 Facade 类之前,问自己:

1. 这个类里的 if-else 是不是跟业务规则有关?是的话搬出去。

2. 这个类的构造函数里注入了超过 5 个依赖?考虑按流程拆。

3. 如果去掉这个类,调用方需要多写多少行代码?如果不到 10 行,这个门面价值不大。

门面应该是薄薄的一层调度器,不是把所有东西搅在一起的大杂烩。

---

我们团队在做一个叫「爪爪代码冒险记」的微信小程序,用卡皮巴拉漫画讲设计模式。外观模式那关被设计成「遥控器」主题——卡皮巴拉用一个遥控器控制整个智能家居系统。如果感兴趣可以搜一下,或者等我后面的文章。

http://www.gsyq.cn/news/1558647.html

相关文章:

  • Native Sparse Attention PyTorch实战指南:Enwik8语言建模完整示例
  • VSCode新窗口背景水印logo修改美化
  • React Table Library可访问性设计:构建符合WCAG标准的无障碍表格
  • OpenClaw零代码AI工作流部署实战:Win/Mac 5分钟启动指南
  • 视频孪生+空间智能大模型 港航口岸航空全域数字化解决方案
  • Akula EVM执行引擎:Rust实现的智能合约虚拟机性能分析
  • tsParticles架构解析:高性能粒子系统的工程实现与优化策略
  • AI专著生成神器推荐!一键生成20万字专著,解决写作效率与质量难题
  • 北京排名前列老牌连锁大型实体犬舍全城5家直营基地靠谱推荐 - 北京同城宠物基地
  • FDC故障检测与分类系统架构深度解析:从传感器数据到实时告警的完整链路
  • MC9S12 BDM调试模块深度解析:从硬件命令到固件命令的实战指南
  • MC9S12VR定时器TIM16B8CV3深度解析:从输入捕获到PWM实战
  • 健康证识别API详解:从在线调试到项目集成
  • 4步掌握Microsoft Foundry Toolkit:零基础构建AI应用的终极指南
  • 3步搭建你的专属Jellyfin媒体服务器:免费开源的家庭影院解决方案
  • 2026深度实测:双AI编码模式vibe coding对比,Work模式与Composer真实开发差异
  • 2026达州本地人必选防水补漏检测维修公司靠谱服务商TOP5推荐:房屋渗漏水检测维修/卫生间/厨房/天花板/阳台/外墙渗漏水检测补漏维修-暗管漏水检测专业仪器精准定位漏水点 - 即刻修防水
  • 如何在Mac上免费搭建专业医学影像工作站:Horos完整指南
  • React应用从运行时CSS-in-JS到编译时CSS的完整迁移实战指南
  • CANN/ge ONNX模型解析接口
  • 揭秘Awesome-Efficient-Reasoning:10大关键技术领域深度解析
  • 2026辽阳本地人必选防水补漏检测维修公司靠谱服务商TOP5推荐:房屋渗漏水检测维修/卫生间/厨房/天花板/阳台/外墙渗漏水检测补漏维修-暗管漏水检测专业仪器精准定位漏水点 - 即刻修防水
  • 2026年值得信赖的旧房翻新公司推荐 体验服务品质之选 避坑指南 - mypinpai
  • CANN/ge SetOutput API文档
  • (2026新)红河正规防水补漏公司口碑榜TOP5权威推荐!卫生间/厨房/阳台/屋顶/天花板/地下室渗漏水检测维修攻略-靠谱漏水检测维修师傅推荐 - 安佳防水
  • 如何用ManiSkill 3分钟搭建高性能机器人仿真环境:GPU加速的终极解决方案
  • CPU部署大模型的三大硬约束与四步落地法
  • TinyKVM与Docker对比分析:何时选择硬件虚拟化
  • 思源宋体:7种字重的开源中文字体技术解析与应用指南
  • 2026全屋整装口碑推荐强势出炉,价格透明零套路,全屋整装看这篇就够 - mypinpai