设计模式——观察者设计模式(行为型)

摘要

本文详细介绍了观察者设计模式,包括其定义、结构、实现方式、适用场景以及实战示例。通过代码示例展示了如何在Spring框架下实现观察者模式,以及如何通过该模式实现状态变化通知。同时,对比了观察者模式与消息中间件在设计理念、耦合程度、通信方式和分布式支持等方面的差异,帮助读者更好地理解和选择合适的实现方式。

1. 观察者设计模式定义

观察者设计模式(Observer Pattern)是一种行为型设计模式,定义了一种一对多的依赖关系,使得当一个对象(被观察者)的状态发生改变时,所有依赖它的对象(观察者)都会得到自动通知并更新,从而实现对象间的松耦合和实时通讯。观察者模式通过定义对象间的发布-订阅关系,实现事件的自动通知和响应,适合事件驱动和异步消息场景。

1.1. 核心定义

角色

  • 被观察者(Subject):维护一组观察者,负责状态的管理和通知。
  • 观察者(Observer):注册到被观察者,一旦被观察者状态变化,接收通知并执行相应动作。

目的:让多个观察者对象自动获得被观察者的状态变化,降低对象间耦合度。

2. 观察者设计模式结构

观察者模式包含如下角色:

  • Subject: 目标
  • ConcreteSubject: 具体目标
  • Observer: 观察者
  • ConcreteObserver: 具体观察者

2.1. 观察者模式类图

2.2. 观察者模式时序图

3. 观察者设计模式实现方式

3.1. 观察者设计模式实现步骤

3.1.1. 定义抽象主题(Subject)接口

  • 提供注册、移除和通知观察者的方法。

    public interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
    }

3.1.2. 定义抽象观察者(Observer)接口

  • 声明一个更新方法,主题状态变化时调用。

    public interface Observer {
    void update(String message);
    }

3.1.3. 具体主题类实现 Subject

  • 维护观察者列表,实现注册、移除、通知逻辑。

  • 当自身状态变化时调用 notifyObservers(),遍历通知所有观察者。

    import java.util.ArrayList;
    import java.util.List;

    public class ConcreteSubject implements Subject {
    private final List observers = new ArrayList<>();
    private String state;

    复制代码
      @Override
      public void registerObserver(Observer observer) {
          observers.add(observer);
      }
    
      @Override
      public void removeObserver(Observer observer) {
          observers.remove(observer);
      }
    
      @Override
      public void notifyObservers() {
          for (Observer observer : observers) {
              observer.update(state);
          }
      }
    
      public void setState(String state) {
          this.state = state;
          notifyObservers();
      }

    }

3.1.4. 具体观察者实现 Observer

  • 实现 update 方法,接收通知并做出响应。

    public class ConcreteObserver implements Observer {

    复制代码
      private String name;
    
      public ConcreteObserver(String name) {
          this.name = name;
      }
    
      @Override
      public void update(String message) {
          System.out.println(name + " 收到通知,状态变为: " + message);
      }

    }

3.2. 观察者测试示例

复制代码
public class ObserverPatternDemo {
    
    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();

        Observer observer1 = new ConcreteObserver("观察者1");
        Observer observer2 = new ConcreteObserver("观察者2");

        subject.registerObserver(observer1);
        subject.registerObserver(observer2);

        subject.setState("状态1");
        subject.setState("状态2");
    }
}

说明

  • 主题(Subject)维护观察者集合,状态变化时通知所有观察者。
  • 观察者实现更新接口,获得主题最新状态。

4. 观察者设计模式适合场景

4.1. ✅ 适合使用观察者设计模式的场景

|-------------|--------------------------------------|
| 适合场景 | 说明 |
| 事件驱动系统 | 事件产生后需要通知多个模块或对象做出响应,如 UI 事件监听、消息推送。 |
| 一对多依赖关系 | 一个对象状态变化需要通知多个依赖对象,如股票价格更新通知所有订阅者。 |
| 松耦合需求 | 希望对象间解耦,观察者与被观察者不直接依赖,实现灵活扩展。 |
| 广播通信 | 需要将消息广播给多个接收者,且接收者可动态增加和移除。 |
| 异步通知 | 状态更新后,通知观察者进行异步处理。 |
| 分布式系统 | 多个服务或模块之间的状态同步与消息推送。 |

4.2. ❌ 不适合使用观察者设计模式的场景

|-----------------|----------------------------|
| 不适合场景 | 说明 |
| 对象之间不存在依赖关系 | 彼此独立的对象无需相互通知。 |
| 通知对象数量固定且简单 | 只有少数固定对象,且耦合关系明确,使用简单调用即可。 |
| 需要严格同步控制的场景 | 观察者通知是异步的,无法满足严格同步时序需求。 |
| 性能敏感场景 | 大量观察者通知导致性能开销大,影响系统响应速度。 |
| 过度使用导致复杂性增加 | 观察者链条过长,维护困难,代码难以理解。 |

总结:观察者模式主要适用于需要"自动广播状态变化给多个对象"且"希望对象间低耦合"的场景。不适合简单调用、同步严格或性能瓶颈明显的场景。

5. 观察者设计模式实战示例

5.1. 场景说明

在风控系统中,当订单状态发生变化(比如风控审核结果出来后),需要通知多个模块(比如日志记录模块、报警模块、缓存更新模块)来响应该状态变化。

5.2. 定义观察者接口

复制代码
public interface OrderStatusObserver {
    void update(String orderId, String status);
}

5.3. 定义具体观察者实现类

复制代码
import org.springframework.stereotype.Component;

@Component
public class LoggingObserver implements OrderStatusObserver {

    @Override
    public void update(String orderId, String status) {
        System.out.println("日志模块:订单 " + orderId + " 状态更新为:" + status);
        // 这里可以写日志持久化操作
    }
}

@Component
public class AlertObserver implements OrderStatusObserver {

    @Override
    public void update(String orderId, String status) {
        if ("REJECTED".equals(status)) {
            System.out.println("报警模块:订单 " + orderId + " 审核拒绝,触发报警!");
            // 触发报警逻辑
        }
    }
}

@Component
public class CacheObserver implements OrderStatusObserver {

    @Override
    public void update(String orderId, String status) {
        System.out.println("缓存模块:更新订单 " + orderId + " 状态缓存为:" + status);
        // 缓存刷新逻辑
    }
}

5.4. 定义主题接口和实现类

复制代码
public interface OrderStatusSubject {
    void registerObserver(OrderStatusObserver observer);
    void removeObserver(OrderStatusObserver observer);
    void notifyObservers(String orderId, String status);
}

import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

@Component
public class OrderStatusSubjectImpl implements OrderStatusSubject {

    private final List<OrderStatusObserver> observers = new ArrayList<>();

    // 使用Spring自动注入所有实现了OrderStatusObserver接口的Bean
    @Autowired
    private List<OrderStatusObserver> observerBeans;

    @PostConstruct
    public void init() {
        // 初始化时,将所有观察者注册进列表
        observers.addAll(observerBeans);
    }

    @Override
    public void registerObserver(OrderStatusObserver observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(OrderStatusObserver observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers(String orderId, String status) {
        for (OrderStatusObserver observer : observers) {
            observer.update(orderId, status);
        }
    }
}

5.5. 4. 业务调用示例(比如风控服务)

复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class RiskControlService {

    @Autowired
    private OrderStatusSubject orderStatusSubject;

    public void auditOrder(String orderId, boolean passed) {
        // 风控业务逻辑
        String status = passed ? "APPROVED" : "REJECTED";
        System.out.println("风控审核完成,订单:" + orderId + ",状态:" + status);

        // 审核完成后,通知所有观察者
        orderStatusSubject.notifyObservers(orderId, status);
    }
}

5.6. 测试Controller(可选)

复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RiskController {

    @Autowired
    private RiskControlService riskControlService;

    @PostMapping("/auditOrder")
    public String auditOrder(@RequestParam String orderId, @RequestParam boolean passed) {
        riskControlService.auditOrder(orderId, passed);
        return "审核完成";
    }
}

5.7. 实战示例总结

  • 观察者接口 OrderStatusObserver,多个实现类由Spring管理并注入。
  • 主题 OrderStatusSubjectImpl 维护观察者列表,利用 @Autowired 自动注入所有观察者Bean。
  • 风控业务执行后调用主题的 notifyObservers(),实现状态变化通知。
  • 全部采用Spring注解管理,未用构造函数注入,代码简洁且易于扩展。

6. 观察者设计模式思考

6.1. 观察着模式和使用消息中间件来通知处理的方式有什么不同?

很好的问题!观察者模式和消息中间件在实现"发布-订阅"或通知机制时都很常用,但它们的设计理念、实现方式和适用场景有显著差别。下面帮你详细分析两者的区别:

|------------|--------------------------------------------|----------------------------------|
| 维度 | 观察者模式 | 消息中间件(如Kafka、RabbitMQ、ActiveMQ等) |
| 设计层次 | 设计模式,属于应用内部的设计范式,通常是在代码内部直接实现。 | 独立的中间件系统,属于基础设施层,提供异步消息传递机制。 |
| 耦合程度 | 被观察者和观察者耦合较紧密,观察者必须知道被观察者实例(尽管解耦设计可以降低耦合)。 | 发布者和订阅者完全解耦,彼此不直接依赖,只通过消息中间件通信。 |
| 通信方式 | 通常是同步调用,状态变化时立即调用观察者的更新方法。 | 典型异步通信,消息发送后,接收方异步消费,支持异步处理和缓冲。 |
| 分布式支持 | 一般用于单体应用或同一JVM内对象之间的通知。 | 天生支持分布式系统,跨进程、跨服务器,甚至跨数据中心的消息传递。 |
| 扩展性 | 观察者数量有限,扩展时需要修改代码或依赖注入配置。 | 高度扩展,支持大量消费者和复杂路由规则,方便动态添加消费者。 |
| 容错与持久化 | 无持久化和容错机制,一旦通知失败可能丢失信息。 | 支持消息持久化、重试、消息确认、死信队列,增强系统可靠性。 |
| 性能影响 | 观察者调用是同步的,影响调用链的执行时间,可能导致性能瓶颈。 | 异步处理,解耦主业务流程,提升系统吞吐量和响应速度。 |
| 使用场景 | 小型系统、UI事件响应、简单状态同步、模块内部事件通知。 | 大型分布式系统、微服务间异步通信、异步任务调度、事件驱动架构。 |

6.1.1. 简单总结:

  • 观察者模式 适合应用内、轻量级的同步通知,设计简单,适合对象之间状态变化通知。
  • 消息中间件 适合跨进程、跨网络、异步且高可靠的消息通信,适合分布式系统异步解耦和复杂事件流转。

6.1.2. 举例对比:

  • 观察者模式:风控模块风控结果出来后,同步通知日志模块和报警模块,立即处理。
  • 消息中间件:风控模块发布"风控结果"消息到消息队列,日志模块和报警模块异步订阅该消息,消息队列保证消息可靠送达和解耦。

博文参考

相关推荐
触底反弹4 小时前
🤯 面试被问 AI Workflow 和 Agent 有啥区别?3 张图 + 2 段代码讲清楚!
人工智能·设计模式·面试
杨充1 天前
10.可测试性实战设计
设计模式·开源·代码规范
杨充1 天前
9.重构十二式的实战
设计模式·开源·代码规范
杨充1 天前
6.设计原则的全景图
设计模式·开源·全栈
杨充1 天前
2.面向对象的特性
设计模式
杨充1 天前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充1 天前
8.反模式与坏味道
设计模式·开源·代码规范
杨充1 天前
3.接口vs抽象类比较
设计模式
咖啡八杯1 天前
文法、BNF与AST
java·设计模式·解释器模式·ast·文法