Skip to content

观察者模式

定义:定义一种一对多的依赖关系,一个主题对象可以被多个观察者对象同时监听,使得每当主题对象状态变化时,所有依赖于它的对象都会得到通知并自动更新

适用场景:

  1. 当一个或者多个对象的变化依赖于另一个对象的变化
  2. 需要实现类似广播的功能,无需知道具体收听者,只需要分发广播,系统中感兴趣的对象会自动接收该广播
  3. 多层嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知

优点:

  1. 观察者和被观察者是松耦合的,只是在抽象层面耦合,符合依赖倒置原则
  2. 实现了一对多的通讯机制,支持事件注册机制,支持兴趣分发机制

缺点:

  1. 如果观察者数量过多,则事件通知会耗时较长
  2. 事件通知呈线性关系,如果其中一个观察者处理时卡住,则影响后续观察者接收该事件
  3. 如果观察者和被观察者之间存在依赖关系,有可能因为循环引用导致系统奔溃

示例

JDK 实现

JDK 默认有提供观察者模式的实现,以论坛提问为例

问题对象,被观察者通知观察者的消息载体

java
public class Question {
    private String userName;
    private String content;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

论坛对象,被观察对象

java
public class Forum extends Observable {
    private String name = "论坛";
    private static final Forum forum = new Forum();

    private Forum() {}

    public static Forum getInstance(){
        return forum;
    }

    public String getName() {
        return name;
    }

    public void publishQuestion(Question question){
        System.out.println(question.getUserName() + "在" + this.name + "上提交了一个问题。");
        setChanged();
        notifyObservers(question);
    }
}

用户对象,观察者对象,需要实现 Observer 中的 update() 方法,表示收到通知后做出的反应

java
public class User implements Observer {

    private String name;

    public User(String name) {
        this.name = name;
    }

    public void update(Observable o, Object arg) {
        Forum forum = (Forum)o;
        Question question = (Question)arg;
        System.out.println("======================");
        System.out.println(name + "你好!\n" +
                        "您收到了一个来自" + gper.getName() + "的提问,希望您解答。问题内容如下:\n" +
                        question.getContent() + "\n" +
                        "提问者:" + question.getUserName());
    }
}

客户端使用,发出用户行为前需要先把观察者 注册 到被观察者对象中。当问题发布后,会自动通知到 tom 和 jerry 用户并调用 update 方法

java
public class Test {
    public static void main(String[] args) {
        Forum forum = Forum.getInstance();
        User tom = new User("Tom");
        User jerry = new User("Jerry");

        forum.addObserver(tom);
        forum.addObserver(jerry);

        //用户行为
        Question question = new Question();
        question.setUserName("张三");
        question.setContent("观察者模式适用于哪些场景?");

        forum.publishQuestion(question);
    }
}

Guava 实现

Guava 这个组件库中也提供了非常方便易用的观察者模式的解决方案

先导入依赖

xml
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>18.0</version>
</dependency>

创建侦听事件

java
public class GuavaEvent {
    // 订阅通知
    @Subscribe
    public void subscribe(String str) {
        System.out.println("执行subscribe方法: " + str);
    }
}

客户端使用

java
public class Test {
    public static void main(String[] args) {
        // 事件总线
        EventBus eventBus = new EventBus();
        GuavaEvent guavaEvent = new GuavaEvent();
        // 注册侦听的事件
        eventBus.register(guavaEvent);
        // 发送通知
        eventBus.post("test");
    }
   
}

观察者模式在源码中的体现

SpringMVC

SpringMVC 中的 ContextLoaderListener 实现了 Servlet中的 ServletContextListener 接口,而 ServletContextListener 接口是继承 JDK 中的 EventListenerContextLoaderListener 主要的作用是在程序启动时加载 Spring 上下文 ApplicationContext

java
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    public ContextLoaderListener() {
    }

    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }

    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
    }

    public void contextDestroyed(ServletContextEvent event) {
        this.closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}