写点什么

一文搞懂设计模式—观察者模式

作者:码农BookSea
  • 2024-02-18
    浙江
  • 本文字数:6380 字

    阅读完需:约 21 分钟

一文搞懂设计模式—观察者模式

本文已收录至 Github,推荐阅读 👉 Java随想录

微信公众号:Java随想录


观察者模式(Observer Pattern)是一种常见的行为型设计模式,用于在对象之间建立一种一对多的依赖关系。当一个对象的状态发生变化时,所有依赖它的对象都将得到通知并自动更新。

使用场景

观察者模式在许多应用中都有广泛的应用,特别是当存在对象之间的一对多关系,并且需要实时通知和更新时,观察者模式非常适用。下面列举几个典型的使用场景:


  • 消息发布/订阅系统:观察者模式可以用于构建消息发布/订阅系统,其中消息发布者充当主题(被观察者),而订阅者则充当观察者。当发布者发布新消息时,所有订阅者都会收到通知并执行相应操作。

  • 用户界面组件:在图形用户界面 (GUI) 开发中,观察者模式常被用于处理用户界面组件之间的交互。当一个组件的状态发生变化时,其他依赖该组件的组件将自动更新以反映新的状态。

  • 股票市场监控:在金融领域,观察者模式可用于实现股票市场监控系统。各个投资者可以作为观察者订阅感兴趣的股票,在股票价格变动时即时收到通知。

  • 事件驱动系统:观察者模式也常用于事件驱动系统中,如图形用户界面框架、游戏引擎等。当特定事件发生时,触发相应的回调函数并通知所有注册的观察者。


以上仅是观察者模式的一些典型使用场景,实际上,只要存在对象之间的依赖关系,并且需要实现解耦和灵活性,观察者模式都可以考虑作为一种设计方案。

实现方式

观察者模式包含以下几个核心角色:


  • 主题(Subject):也称为被观察者或可观察者,它是具有状态的对象,并维护着一个观察者列表。主题提供了添加、删除和通知观察者的方法。

  • 观察者(Observer):观察者是接收主题通知的对象。观察者需要实现一个更新方法,当收到主题的通知时,调用该方法进行更新操作。

  • 具体主题(Concrete Subject):具体主题是主题的具体实现类。它维护着观察者列表,并在状态发生改变时通知观察者。

  • 具体观察者(Concrete Observer):具体观察者是观察者的具体实现类。它实现了更新方法,定义了在收到主题通知时需要执行的具体操作。


下面是观察者模式的经典实现方式:


  1. 定义观察者接口:创建一个名为 Observer 的接口,包含一个用于接收通知的方法,例如 update()


public interface Observer {    void update();}
复制代码


  1. 定义主题接口:创建一个名为 Subject 的接口,包含用于管理观察者的方法,如 registerObserver()removeObserver()notifyObservers()


public interface Subject {    void registerObserver(Observer observer);    void removeObserver(Observer observer);    void notifyObservers();}
复制代码


  1. 实现具体主题:创建一个具体类实现 Subject 接口,实现注册、移除和通知观察者的方法。在状态变化时调用 notifyObservers() 方法通知所有观察者。


import java.util.ArrayList;import java.util.List;
public class ConcreteSubject implements Subject { private final List<Observer> observers = new ArrayList<>(); private int state;
public void setState(int state) { this.state = state; notifyObservers(); }
@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(); } }}
复制代码


  1. 实现具体观察者:创建一个具体类实现 Observer 接口,实现接收通知并进行相应处理的方法。


public class ConcreteObserver implements Observer {    private String name;    private Subject subject;
public ConcreteObserver(String name, Subject subject) { this.name = name; this.subject = subject; }
@Override public void update() { System.out.println(name+" received notification"); }}
复制代码


  1. 使用观察者模式:在实际代码中,我们可以创建具体的主题和观察者对象,并进行注册和触发状态变化。


public class Main {    public static void main(String[] args) {        ConcreteSubject subject = new ConcreteSubject();
ConcreteObserver observer1 = new ConcreteObserver("Observer 1", subject); ConcreteObserver observer2 = new ConcreteObserver("Observer 2", subject);
subject.registerObserver(observer1); subject.registerObserver(observer2);
subject.setState(10); // Output: // Observer 1 received notification // Observer 2 received notification
subject.removeObserver(observer1);
subject.setState(20); // Output: // Observer 2 received notification }}
复制代码

Java 对观察者模式的支持

观察者模式在 Java 语言中的地位非常重要。在 JDK 的 java.util 包中,提供 Observable 类以及 Observer 接口,它们构成了 Java 语言对观察者模式的支持。


使用 Observable 类以及 Observer 接口,优化之后的代码为:


// 具体观察者public class ConcreteObserver implements Observer {    private String name;
public ConcreteObserver(String name) { // 设置每一个观察者的名字 this.name = name; }
/** * 当变化之后,就会自动触发该方法 */ @Override public void update(Observable o, Object arg) { if (arg instanceof Integer) { System.out.println(this.name + " 观察到 state 更改为:" + arg); } }}
复制代码


// 被观察者,继承 Observable 表示可以被观察public class ConcreteSubject extends Observable {    private int state;
public ConcreteSubject(int state) { this.setState(state); }
public int getState() { return state; }
public void setState(int state) { // 设置变化点 super.setChanged(); // 状态变化,通知观察者 super.notifyObservers(state); this.state = state; }
@Override public String toString() { return "state:" + this.state; }}
复制代码


public class TestObserve {    public static void main(String[] args) {        // 创建被观察者        ConcreteSubject subject = new ConcreteSubject(0);        // 创建观察者        ConcreteObserver ConcreteObserverA = new ConcreteObserver("观察者 A");        ConcreteObserver ConcreteObserverB = new ConcreteObserver("观察者 B");        ConcreteObserver ConcreteObserverC = new ConcreteObserver("观察者 C");        // 添加可观察对象        subject.addObserver(ConcreteObserverA);        subject.addObserver(ConcreteObserverB);        subject.addObserver(ConcreteObserverC);
System.out.println(subject); // Output: // state:0 subject.setState(1); // Output: // 观察者 C 观察到 state 更改为:1 // 观察者 B 观察到 state 更改为:1 // 观察者 A 观察到 state 更改为:1 System.out.println(subject); // Output: // state:1
}}
复制代码

Guava 对观察者模式的支持

Guava 中使用 Event Bus 来实现对观察者模式的支持。


com.google.common.eventbus.EventBus 提供了以下主要方法:


  • register(Object listener):将一个对象注册为事件的监听器。

  • unregister(Object listener):从事件总线中注销一个监听器。

  • post(Object event):发布一个事件到事件总线,以便通知所有注册的监听器。

  • getSubscribers(Class<?> eventClass):返回订阅指定事件类型的所有监听器的集合。


这些方法提供了事件的注册、注销、发布和获取监听器等功能,使得开发者可以方便地使用 EventBus 进行事件驱动编程。


@Getter@AllArgsConstructorpublic class MyEvent {    private String message;}
复制代码


@Slf4jpublic class EventSubscriber {    @Subscribe    public void handleEvent(MyEvent event) {        String message = event.getMessage();        // Handle the event logic        log.info("Received event: " + message);    }}
复制代码


@Testpublic void test() {        EventBus eventBus = new EventBus();        EventSubscriber subscriber = new EventSubscriber();        eventBus.register(subscriber);
// Publish an event eventBus.post(new MyEvent("Hello, World!")); // Output: // Received event: Hello, World! }
复制代码

Spring 对观察者模式的支持

Spring 中可以使用 Spring Event 来实现观察者模式。


在 Spring Event 中,有一些核心的概念和组件,包括 ApplicationEvent、ApplicationListener、ApplicationContext 和 ApplicationEventMulticaster。


  • ApplicationEvent(应用事件):

  • ApplicationEvent 是 Spring Event 框架中的基础类,它是所有事件类的父类。

  • 通过继承 ApplicationEvent,并定义自己的事件类,可以创建特定类型的事件对象。

  • 事件对象通常包含与事件相关的信息,例如状态变化、操作完成等。

  • ApplicationListener(应用监听器):

  • ApplicationListener 是 Spring Event 框架中的接口,用于监听并处理特定类型的事件。

  • 通过实现 ApplicationListener 接口,并指定感兴趣的事件类型,可以创建具体的监听器。

  • 监听器可以定义在任何 Spring Bean 中,当所监听的事件被发布时,监听器会自动接收到该事件,并执行相应的处理逻辑。

  • ApplicationContext(应用上下文):

  • ApplicationContext 是 Spring 框架的核心容器,它负责管理 Bean 的生命周期和依赖关系。

  • 在 Spring Event 中,ApplicationContext 是事件的发布者和订阅者的容器。

  • 通过获取 ApplicationContext 实例,可以获取 ApplicationEventPublisher 来发布事件,也可以注册 ApplicationListener 来监听事件。

  • ApplicationEventMulticaster(事件广播器):

  • ApplicationEventMulticaster 是 Spring Event 框架中的组件,用于将事件广播给各个监听器。

  • 它负责管理事件和监听器之间的关系,并将事件传递给对应的监听器进行处理。

  • Spring 框架提供了几种实现 ApplicationEventMulticaster 的类,如 SimpleApplicationEventMulticaster 和 AsyncApplicationEventMulticaster,用于支持不同的事件分发策略。


通过使用这些关键概念和组件,可以在 Spring 应用程序中实现事件驱动的编程模型。事件发布者(ApplicationEventPublisher)可以发布特定类型的事件,而订阅者(ApplicationListener)可以监听和处理已发布的事件。ApplicationContext 作为容器,负责管理事件和监听器,并使用 ApplicationEventMulticaster 来实现事件的广播和分发。


下面是使用 Spring Event 实现观察者模式的例子:


/** * <p> * 基础事件发布类 * </p> * */
public abstract class BaseEvent<T> extends ApplicationEvent {
/** * 该类型事件携带的信息 */ private T eventData;
/** * * @param source 最初触发该事件的对象 * @param eventData 该类型事件携带的信息 */ public BaseEvent(Object source, T eventData) { super(source); this.eventData = eventData; }
public T getEventData() { return eventData; }}
复制代码


这里定义了一个基础事件发布抽象类,所有的事件发布类都可以继承此类。


@Data@Builder@AllArgsConstructor@NoArgsConstructorpublic  class User {    private Integer userId;    private String userName;}
复制代码


public class UserEvent  extends BaseEvent<User>{    private static final long serialVersionUID = 8145130999696021526L;
public UserEvent(Object source, User user) { super(source,user); }
}
复制代码


@Slf4j@Servicepublic class UserListener {    /*     * @Async加了就是异步监听,没加就是同步(启动类要开启@EnableAsync注解)     * 可以使用@Order定义监听者顺序,默认是按代码书写顺序     * 如果返回类型不为void,则会被当成一个新的事件,再次发布     * @EventListener注解在EventListenerMethodProcessor类被扫描     * 可以使用SpEL表达式来设置监听器生效的条件     * 监听器可以看做普通方法,如果监听器抛出异常,在publishEvent里处理即可     */
//@Async @Order(1) @EventListener(condition = "#userEvent.getEventData().getUserName().equals('小明')") public String lister1(UserEvent userEvent){ User user =userEvent.getEventData(); log.info(user.toString()); return "小米"; }
@Async @Order(2) @EventListener public void lister3(UserEvent userEvent){ log.info("监听者2"); } @Async @Order(3) @EventListener public void lister2(String name){ log.info("我叫:"+name); } }
复制代码


@Slf4j@SpringBootTest@RunWith(SpringRunner.class)public class ObserveTest {    @Resource    private ApplicationEventPublisher applicationEventPublisher;    @Test    public void test() {        applicationEventPublisher.publishEvent(new UserEvent(this, new User(1, "小明")));        // Output:        // User(userId=1, userName=小明)        // 我叫:小米        // 监听者2    }
}
复制代码


IDEA 中可以直接跳转到对应的监听器。



相比于 Guava Event Bus,Spring Event 在实现观察者模式时具有以下优点:


  • 集成性:Spring Event 是 Spring 框架的一部分,可以与其他 Spring 组件(如 Spring Boot、Spring MVC 等)无缝集成。这使得在一个应用程序中使用 Spring Event 变得更加方便和统一。

  • 注解驱动:Spring Event 支持使用注解来声明事件监听器和发布事件。通过使用 @EventListener 注解,开发人员可以轻松定义事件监听器方法,并且不需要显式注册和注销监听器。

优缺点

观察者模式有以下几个优点:


  • 解耦性:观察者模式能够将主题和观察者之间的耦合度降到最低。主题与观察者之间都是松散耦合的关系,它们之间可以独立地进行扩展和修改,而不会相互影响。

  • 灵活性:通过使用观察者模式,可以动态地添加、删除和通知观察者,使系统更加灵活。无需修改主题或观察者的代码,就可以实现新的观察者加入和旧观察者离开的功能。

  • 一对多关系:观察者模式支持一对多的依赖关系,一个主题可以有多个观察者。这样可以方便地实现消息的传递和广播,当主题状态更新时,所有观察者都能得到通知。


虽然观察者模式具有许多优点,但也存在一些缺点:


  • 可能引起性能问题:如果观察者较多或通知过于频繁,可能会导致性能问题。每个观察者都需要接收通知并执行相应操作,当观察者较多时,可能会增加处理时间和系统负载。

  • 可能引起循环依赖:由于观察者之间可以相互注册,如果设计不当,可能会导致循环依赖的问题。这样会导致触发通知的死循环,造成系统崩溃或异常。

  • 顺序不确定性:在观察者模式中,观察者的执行顺序是不确定的。如果观察者之间有依赖关系,可能会产生意外的结果。


综上所述,观察者模式在许多场景下都非常有用,但在使用时需要注意性能问题、循环依赖和执行顺序等方面的考虑。

发布于: 刚刚阅读数: 5
用户头像

码农BookSea

关注

Java开发工程师 2021-12-26 加入

Java开发菜鸟工程师,写博客的初衷是为了沉淀我所学习,累积我所见闻,分享我所体验。希望和更多的人交流学习。

评论

发布
暂无评论
一文搞懂设计模式—观察者模式_Java_码农BookSea_InfoQ写作社区