设计模式探索:观察者模式

1. 观察者模式

1.1 什么是观察者模式

观察者模式用于建立一种对象与对象之间的依赖关系,当一个对象发生改变时将自动通知其他对象,其他对象会相应地作出反应。

在观察者模式中有如下角色:

  • Subject(抽象主题/被观察者): 抽象主题角色把所有观察者对象保存在一个集合里,每个主题可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
  • ConcreteSubject(具体主题/具体被观察者): 该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
  • Observer(抽象观察者): 观察者的抽象类,定义了一个更新接口,使得在得到主题更改通知时更新自己。
  • ConcreteObserver(具体观察者): 实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。在具体观察者中维护一个指向具体目标对象的引用,存储具体观察者的有关状态,这些状态需要与具体目标保持一致。
1.2 观察者模式实现
  • 观察者
java 复制代码
/**
 * 抽象观察者
 */
public interface Observer {
    // update方法: 为不同观察者的更新(响应)行为定义相同的接口,不同的观察者对该方法有不同的实现
    void update();
}

/**
 * 具体观察者
 */
public class ConcreteObserverOne implements Observer {
    @Override
    public void update() {
        // 获取消息通知,执行业务代码
        System.out.println("ConcreteObserverOne 得到通知!");
    }
}

/**
 * 具体观察者
 */
public class ConcreteObserverTwo implements Observer {
    @Override
    public void update() {
        // 获取消息通知,执行业务代码
        System.out.println("ConcreteObserverTwo 得到通知!");
    }
}
  • 被观察者
java 复制代码
/**
 * 抽象目标类
 */
public interface Subject {
    void attach(Observer observer);
    void detach(Observer observer);
    void notifyObservers();
}

/**
 * 具体目标类
 */
public class ConcreteSubject implements Subject {
    // 定义集合,存储所有观察者对象
    private ArrayList<Observer> observers = new ArrayList<>();

    // 注册方法,向观察者集合中增加一个观察者
    @Override
    public void attach(Observer observer) {
        observers.add(observer);
    }

    // 注销方法,用于从观察者集合中删除一个观察者
    @Override
    public void detach(Observer observer) {
        observers.remove(observer);
    }

    // 通知方法
    @Override
    public void notifyObservers() {
        // 遍历观察者集合,调用每一个观察者的响应方法
        for (Observer obs : observers) {
            obs.update();
        }
    }
}
  • 测试类
java 复制代码
public class Client {
    public static void main(String[] args) {
        // 创建目标类(被观察者)
        ConcreteSubject subject = new ConcreteSubject();

        // 注册观察者类,可以注册多个
        subject.attach(new ConcreteObserverOne());
        subject.attach(new ConcreteObserverTwo());

        // 具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
        subject.notifyObservers();
    }
}

2. 发布订阅模式与观察者模式的区别

2.1 定义上的不同

发布订阅模式属于广义上的观察者模式。

  • 发布订阅模式是最常用的一种观察者模式的实现,从解耦和重用角度来看,更优于典型的观察者模式。
2.2 两者的区别

我们来看一下观察者模式与发布订阅模式结构上的区别

操作流程上的区别

  • 观察者模式:数据源直接通知订阅者发生改变。
  • 发布订阅模式:数据源告诉第三方(事件通道)发生了改变,第三方再通知订阅者发生了改变。

3. 观察者模式在实际开发中的应用

3.1 实际开发中的需求场景

在我们日常业务开发中,观察者模式的一个重要作用在于实现业务的解耦 。以用户注册的场景为例,假设在用户注册完成时,需要给该用户发送邮件、发送优惠券等操作,如下图所示:

使用观察者模式之后

  • UserService 在完成自身的用户注册逻辑之后,仅需要发布一个 UserRegisterEvent 事件,而无需关注其它拓展逻辑。
  • 其它 Service 可以自己 订阅 UserRegisterEvent 事件,实现自定义的拓展逻辑。

3.2 Spring事件机制

Spring 基于观察者模式,实现了自身的事件机制,由三部分组成:

  • 事件 ApplicationEvent :通过继承 它,实现自定义事件。另外,通过它的 source 属性可以获取事件timestamp 属性可以获得发生时间。
  • 事件发布者 ApplicationEventPublisher:通过它,可以进行事件的发布。
  • 事件监听器 ApplicationListener :通过实现它,进行指定类型的事件的监听。
3.3 代码实现

(1) UserRegisterEvent

  • 创建 UserRegisterEvent 事件类,继承 ApplicationEvent 类,用户注册事件。代码如下:
java 复制代码
/**
 * 用户注册事件
 */
public class UserRegisterEvent extends ApplicationEvent {
  
    private String username;

    public UserRegisterEvent(Object source) {
        super(source);
    }

    public UserRegisterEvent(Object source, String username) {
        super(source);
        this.username = username;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

(2) UserService (事件源+事件发布)

  • 创建 UserService 类,代码如下:
java 复制代码
/**
 * 事件源角色+事件发布
 */
@Service
public class UserService implements ApplicationEventPublisherAware {

    private Logger logger = LoggerFactory.getLogger(getClass());
    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public void register(String username){
        // 执行注册逻辑
        logger.info("[register][执行用户{}的注册逻辑]", username);
        // 发布用户注册事件
        applicationEventPublisher.publishEvent(new UserRegisterEvent(this, username));
    }
}
  • 实现 ApplicationEventPublisherAware 接口,从而将 ApplicationEventPublisher 注入到其中。
  • 在执行完注册逻辑后,调用 ApplicationEventPublisherpublishEvent(ApplicationEvent event) 方法,发布 UserRegisterEvent 事件。

(3) 创建 EmailService

java 复制代码
/**
 * 事件监听角色
 */
@Service
public class EmailService implements ApplicationListener<UserRegisterEvent> {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public void onApplicationEvent(UserRegisterEvent event) {
        logger.info("[onApplicationEvent][给用户({}) 发送邮件]", event.getUsername());
    }
}
  • 实现 ApplicationListener 接口,通过 E 泛型设置感兴趣的事件。
  • 实现 onApplicationEvent(E event) 方法,针对监听的 UserRegisterEvent 事件,进行自定义处理。

(4) CouponService

java 复制代码
@Service
public class CouponService {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @EventListener 
    public void addCoupon(UserRegisterEvent event) {
        logger.info("[addCoupon][给用户({}) 发放优惠劵]", event.getUsername());
    }
}
  • 添加 @EventListener 注解,并设置监听的事件为 UserRegisterEvent

(5) DemoController

  • 提供 /demo/register 注册接口
java 复制代码
@RestController
@RequestMapping("/demo")
public class DemoController {

    @Autowired
    private UserService userService;

    @GetMapping("/register")
    public String register(String username) {
        userService.register(username);
        return "success";
    }
}
3.4 代码测试
  1. 执行 DemoApplication 类,启动项目。
  2. 调用 http://127.0.0.1:8080/demo/register?username=mashibing 接口,进行注册。IDEA 控制台打印日志如下:
java 复制代码
// UserService 发布 UserRegisterEvent 事件
2023-04-19 16:49:40.628  INFO 9800 --- [nio-8080-exec-1] c.m.d.o.demo02.service.UserService       : [register][执行用户mashibing的注册逻辑]

// EmailService 监听处理该事件
2023-04-19 16:49:40.629  INFO 9800 --- [nio-8080-exec-1] c.m.d.o.demo02.listener.EmailService     : [onApplicationEvent][给用户(mashibing) 发送邮件]

// CouponService 监听处理该事件
2023-04-19 16:49:40.629  INFO 9800 --- [nio-8080-exec-1] c.m.d.o.demo02.listener.CouponService    : [addCoupon][给用户(mashibing) 发放优惠劵]

4. 观察者模式总结

1) 观察者模式的优点

  • 降低目标类和观察者之间的耦合
  • 可以实现广播机制

2) 观察者模式的缺点

  • 通知的发送会消耗一定的时间
  • 如果被观察者有循环依赖,会导致系统的崩溃

3) 观察者模式常见的使用场景

  • 一个对象的改变,需要改变其他对象的时候
  • 一个对象的改变,需要进行通知的时候
相关推荐
老蒋每日coding7 小时前
AI Agent 设计模式系列(十九)—— 评估和监控模式
人工智能·设计模式
会员果汁8 小时前
23.设计模式-解释器模式
设计模式·解释器模式
「QT(C++)开发工程师」15 小时前
C++设计模式
开发语言·c++·设计模式
茶本无香15 小时前
设计模式之七—装饰模式(Decorator Pattern)
java·设计模式·装饰器模式
漂洋过海的鱼儿1 天前
设计模式——EIT构型(三)
java·网络·设计模式
老蒋每日coding1 天前
AI Agent 设计模式系列(十八)—— 安全模式
人工智能·安全·设计模式
老蒋每日coding2 天前
AI Agent 设计模式系列(十六)—— 资源感知优化设计模式
人工智能·设计模式·langchain
老蒋每日coding2 天前
AI Agent 设计模式系列(十七)—— 推理设计模式
人工智能·设计模式
冷崖2 天前
桥模式-结构型
c++·设计模式
连山齐名2 天前
设计模式之一——堵塞队列
设计模式