设计模式与架构设计:分层架构中的外观、微服务中的代理、事件驱动中的观察者

说实话,很多朋友学设计模式时,总觉得这是“玩具代码”。

一个模式一个例子,背得滚瓜烂熟,一到真实项目就懵了。

为什么?因为模式不是孤立存在的。它们要跟架构结合,才能发挥真正威力。

今天我就拿三个最常见的架构场景,聊聊外观、代理、观察者这三个模式是怎么“实战”的。

一、分层架构中的外观模式(Facade)

先说说分层架构。这是最经典的架构之一,Controller、Service、DAO 三层。

但有个问题:Controller 层如果直接调用多个 Service,代码会变得很“碎”。

比如用户注册,要调用户 Service、积分 Service、通知 Service……

Controller 里写一堆调用,耦合高,还不好测。

外观模式的核心思路: 给子系统提供一个统一的入口,简化调用方的工作。

我在项目中遇到过类似场景。当时有个订单模块,前端需要同时查订单、查物流、查优惠券。一开始 Controller 里写了三个 Service 调用,后来需求一变,改得我头皮发麻。

后来我加了一个 OrderFacade,把这三个查询封装成一个方法。前端只调一次,后端内部随便改,前端不用动。

// Java 示例:外观模式在分层架构中的应用
public class OrderFacade {
    private OrderService orderService;
    private LogisticsService logisticsService;
    private CouponService couponService;

    public OrderDetailVO getOrderDetail(String orderId) {
        Order order = orderService.getById(orderId);
        Logistics logistics = logisticsService.queryByOrderId(orderId);
        List<Coupon> coupons = couponService.getAvailableCoupons(order.getUserId());
        return new OrderDetailVO(order, logistics, coupons);
    }
}

说白了,外观模式就是给复杂子系统“包一层皮”。

Controller 只跟 Facade 打交道,不用关心内部有多少 Service。

我的习惯: 每个业务模块至少有一个 Facade 类。哪怕现在只有一个 Service,也先建好。后面扩展时,你会感谢这个决定。

二、微服务中的代理模式(Proxy)

微服务架构里,服务之间要远程调用。直接写 HTTP 请求?太原始了。

我们通常用 Feign、gRPC 这类 RPC 框架。它们本质上就是代理模式。

代理模式,说白了就是“替身”。客户端不直接访问真实对象,而是通过代理来访问。

代理可以在调用前后加一些额外逻辑,比如负载均衡、熔断、日志、鉴权。

// Java 示例:微服务中的代理模式(Feign 客户端)
@FeignClient(name = "user-service", url = "http://user-service:8080")
public interface UserServiceProxy {
    @GetMapping("/user/{id}")
    User getUserById(@PathVariable("id") Long id);
}

// 调用方只需要注入这个代理接口
@Service
public class OrderService {
    @Autowired
    private UserServiceProxy userServiceProxy;

    public void createOrder(Long userId) {
        User user = userServiceProxy.getUserById(userId);
        // 业务逻辑...
    }
}

你看,调用方根本不知道背后是 HTTP 调用还是本地调用。代理把远程调用的复杂性隐藏了。

我曾经踩过的坑: 一开始没给代理接口加熔断配置。结果下游服务挂了,上游也跟着雪崩。后来加了 Sentinel 或 Hystrix,才算稳下来。代理模式虽然方便,但别忘了加保护。

代理模式在微服务里还有一个常见应用:API 网关。网关就是所有微服务的统一代理入口,负责路由、限流、认证。

三、事件驱动中的观察者模式(Observer)

事件驱动架构,现在越来越流行。比如用户下单后,要发短信、发邮件、更新库存、记录日志。

如果把这些逻辑都写在订单 Service 里,代码会变得又长又臭。

观察者模式就是解决这个问题的:一个事件发生,多个监听者自动响应。

// Java 示例:观察者模式在事件驱动中的应用
// 事件
public class OrderCreatedEvent {
    private Long orderId;
    private Long userId;
    // getter/setter...
}

// 事件发布者
@Component
public class EventPublisher {
    @Autowired
    private ApplicationEventPublisher publisher;

    public void publishOrderCreated(OrderCreatedEvent event) {
        publisher.publishEvent(event);
    }
}

// 观察者(监听者)
@Component
public class SmsListener {
    @EventListener
    public void handleOrderCreated(OrderCreatedEvent event) {
        System.out.println("发送短信给用户:" + event.getUserId());
    }
}

@Component
public class InventoryListener {
    @EventListener
    public void handleOrderCreated(OrderCreatedEvent event) {
        System.out.println("扣减库存,订单ID:" + event.getOrderId());
    }
}

Spring 的 @EventListener 就是观察者模式的典型实现。发布者只管发事件,监听者各自处理自己的逻辑。

这样做的好处很明显:

  • 解耦: 订单 Service 不用知道谁在监听
  • 可扩展: 加一个新监听者,不用改现有代码
  • 异步: 可以配合线程池或消息队列,实现异步处理
我建议: 如果监听者逻辑比较重,或者需要保证可靠性,别直接用内存事件。改用消息队列(RabbitMQ、Kafka),观察者模式的思想不变,但底层更健壮。

四、三种模式的关系与对比

这三种模式,其实代表了三种不同的架构思想:

模式 架构场景 核心作用 我常用的场景
外观模式 分层架构 简化调用,统一入口 Controller 层封装多个 Service 调用
代理模式 微服务架构 远程调用,增强控制 Feign 客户端、API 网关
观察者模式 事件驱动架构 解耦,异步响应 订单事件、通知推送

你想想看,这三种模式其实都在做同一件事:分离关注点

外观模式把调用方和子系统分离;代理模式把客户端和真实服务分离;观察者模式把事件发布者和监听者分离。

架构设计的本质,不就是把复杂的东西拆开,再通过模式把它们优雅地连起来吗?

五、一张图看懂三种模式在架构中的位置

下面这张 SVG 图,展示了三种模式在典型架构中的位置和作用。我画的时候特意把层次分清楚,方便你对照理解。

三种设计模式在架构中的位置 分层架构 Controller ↓ 调用 Facade OrderFacade 封装多个 Service 微服务架构 客户端 ↓ 调用 Proxy UserServiceProxy 远程调用 + 熔断 事件驱动架构 事件发布者 ↓ 发布事件 OrderCreatedEvent 多个 Listener 响应 三种模式的共同目标:分离关注点 外观模式 简化调用方与子系统的交互 代理模式 控制对远程服务的访问 观察者模式 解耦事件发布与处理逻辑 架构设计 = 合理选择模式 + 灵活组合 没有银弹,只有最适合当前场景的解决方案

嗯,这张图把三种模式在架构中的位置和关系画清楚了。你可以保存下来,以后做架构设计时对照着看。

六、避坑指南与个人经验

最后,分享几个我这些年积累的经验:

  • 外观模式别滥用: 不是所有 Service 调用都要封装。如果只是简单的一对一调用,加 Facade 反而多余。我一般等 Controller 里出现三个以上 Service 调用时,才考虑加 Facade。
  • 代理模式注意性能: 远程代理毕竟有网络开销。别在循环里调代理接口,能批量查询就批量查。我曾经见过一个同事在 for 循环里调 Feign 接口,结果接口响应从 200ms 变成 20s。
  • 观察者模式注意异常处理: 一个监听者抛异常,会不会影响其他监听者?Spring 默认是同步的,一个挂了,后面的都不执行。我建议要么用异步,要么在监听者内部 try-catch。
一句话总结: 外观模式让调用变简单,代理模式让远程变透明,观察者模式让扩展变容易。三种模式,三种架构,一个目标——写出好维护、好扩展的代码。

好了,这一章就聊到这儿。模式是死的,架构是活的。多在实际项目中琢磨,你自然就知道什么时候该用哪个模式了。