设计模式篇-观察者模式

目录

定义

观察者模式(Observer):定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

   
   
   
   
  • 1

上面是官方给出的观察者模式的定义,从我个人角度来讲,我觉得就把它称之为发布-订阅模式更为贴切。虽然看起来这个设计模式没什么难度,但是想把它真正应用到日常的代码开发中,还是得下一番功夫。本文首先会使用一个简单的例子来引导大家,让大家明白什么是观察者模式,然后再给大家讲解一下spring框架是如何使用这个观察者模式的(由于本人对于java语言更为熟悉,所以接下来的讲解全部使用的语言都是java)。

java内置观察者模式实现

我们可以自己实现观察者模式,也可以使用java语言本身内置的观察者模式,通过java.util包中的Observer接口(观察者)与Observable类(被观察者)实现。接下来将使用java内置的观察者模式实现一个天气预报发布和订阅的小例子。
设计模式篇-观察者模式_第1张图片
接下来我们实现上图天气预告的发布订阅。

  1. 定义一个天气预告发布者,需要继承Observable类;当发布天气预告信息时,所有的订阅者都会受到天气信息。
package observation;

import java.util.Observable;

/**

  • 天气预告发布者 发布天气信息给订阅者
    */
    public class WeatherPublisher extends Observable {

    //天气信息
    private String weatherInfo;

    //订阅者使用该方法主动获取天气信息
    public String getWeatherInfo() {
    return weatherInfo;
    }

    public void setWeatherInfo(String weatherInfo) {
    this.weatherInfo = weatherInfo;
    //在通知订阅者前必须调用该方法指示状态的改变
    //为什么要调用该方法,我们且看后面的讲解
    this.setChanged();
    //采用“拉”的方式,订阅者主动拉取天气的变化信息
    this.notifyObservers();
    //与上面的相面,采用“推”的方式,将天气信息推送给订阅者
    // this.notifyObservers(weatherInfo);
    }

    public static void main(String[] args) {
    //定义一个天气预告发布者
    WeatherPublisher weatherPublisher = new WeatherPublisher();

     //添加小王和小李为天气预告订阅者
     weatherPublisher.addObserver(new XiaoLiSubscriber());
     weatherPublisher.addObserver(new XiaoWangSubscriber());
    
     //发布天气信息
     weatherPublisher.setWeatherInfo("今天天气很晴朗,阳光明媚,适合出游");
     weatherPublisher.setWeatherInfo("明天是阴天,记得出门带伞哦");
    

    }
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  1. 定义天气预告订阅者小王和小李,需要实现Observer接口;
    天气订阅者小王:
package observation;

import java.util.Observable;
import java.util.Observer;

/**

  • 天气订阅者小王
    */
    public class XiaoWangSubscriber implements Observer {

    //第二个参数即是使用“推”方式推送过来的天气信息,这里使用“拉”方式主动获取
    public void update(Observable o, Object arg) {
    if (o instanceof WeatherPublisher) {
    WeatherPublisher publisher = (WeatherPublisher) o;
    System.out.println(“小王主动获取天气信息:” + publisher.getWeatherInfo());
    }
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

天气订阅者小李:

package observation;

import java.util.Observable;
import java.util.Observer;

/**

  • 天气订阅者小李
    */
    public class XiaoLiSubscriber implements Observer {

    public void update(Observable o, Object arg) {
    if (o instanceof WeatherPublisher) {
    WeatherPublisher publisher = (WeatherPublisher) o;
    System.out.println(“小李主动获取天气信息:” + publisher.getWeatherInfo());
    }
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

运行结果如下:

小王主动获取天气信息:今天天气很晴朗,阳光明媚,适合出游
小李主动获取天气信息:今天天气很晴朗,阳光明媚,适合出游
小王主动获取天气信息:明天是阴天,记得出门带伞哦
小李主动获取天气信息:明天是阴天,记得出门带伞哦

   
   
   
   
  • 1
  • 2
  • 3
  • 4

其实上面的例子是很简单的,打开Observable类的源码仔细阅读就能明白。这里我们贴一下Observable类的伪代码:
设计模式篇-观察者模式_第2张图片
从这里能看出来,为什么我们在通知前必须要调用setChange()这个方法,只有change表只为true时才能通知观察者。

java内置观察者模式的缺点

  1. java.util.Observable的黑暗面
    Observable是一个类,而不是一个接口,这点就很尴尬了,java的类只能继承一个类,无法继承多个类,也就无法同时具有该类和其它超类的行为。这点违反了面向对象编程的原则(针对接口编程,不针对实现编程)。而且Observable类也并未实现其它任何接口,所以你无法建立自己的实现,和Java内置的Observer API搭配使用。如果它无法满足你的需求,也可以自己实现一套观察者模式。
  2. 观察者数量很多耗费时间
    当被观察者状态发生变化会通知所有观察者,如果直接和间接观察者的数量很多,将会耗费很多时间。

自己实现观察者模式

上面说到jdk内置的观察者模式的一些缺点,当它无法满足使用者的需求时,我们可以自己实现一套。实现的方式并不困难,我们仍然使用上面天气预告的例子。实现的方式其实和jdk内置的观察者模式差不多,但是根据业务的不同,会有细节上的差别。

  1. 天气发布者接口
    这里和Observable类就有所不同,我们使用接口而不是类。可以根据不同的业务需求创建发布者。
public interface Subject {
    //注册观察者
    public void registerObserver(Observer observer);
    //删除观察者
    public void removeObserver(Observer observer);
    //通知所有观察者
    public void notifyObservers();
}

   
   
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
public class WeatherSubject implements Subject {
private List<Observer> observers;
private String weatherInfo;

public WeatherSubject() {
    this.observers = new ArrayList<Observer>();
}

public void registerObserver(Observer observer) {
    observers.add(observer);
}

public void removeObserver(Observer observer) {
    observers.remove(observer);
}

public void notifyObservers() {
    for (Observer observer : observers) {
        observer.update(weatherInfo);
    }
}

public String getWeatherInfo() {
    return weatherInfo;
}

public void setWeatherInfo(String weatherInfo) {
    this.weatherInfo = weatherInfo;
    notifyObservers();
}

public static void main(String[] args) {
    WeatherSubject weatherSubject = new WeatherSubject();
    weatherSubject.registerObserver(new XiaoWangObserver());
    weatherSubject.registerObserver(new XiaoLiObserver());
    weatherSubject.setWeatherInfo("今天天气很晴朗,阳光明媚,适合出游");
    weatherSubject.setWeatherInfo("明天是阴天,记得出门带伞哦");
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  1. 天气预告订阅者
public interface Observer {
    public void update(String wetherInfo);
}

   
   
   
   
  • 1
  • 2
  • 3
public class XiaoLiObserver implements Observer {
    public void update(String wetherInfo) {
        System.out.println("小李收到天气信息:" + wetherInfo);
    }
}

   
   
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
public class XiaoWangObserver implements Observer {
    public void update(String wetherInfo) {
        System.out.println("小王收到天气信息:" + wetherInfo);
    }
}

   
   
   
   
  • 1
  • 2
  • 3
  • 4
  • 5

运行结果如下:

小王收到天气信息:今天天气很晴朗,阳光明媚,适合出游
小李收到天气信息:今天天气很晴朗,阳光明媚,适合出游
小王收到天气信息:明天是阴天,记得出门带伞哦
小李收到天气信息:明天是阴天,记得出门带伞哦

   
   
   
   
  • 1
  • 2
  • 3
  • 4

看完这些,相信大家一定对观察者模式有一定的了解。下面我们就来看一看sping框架中是怎么使用观察者模式的。

spring是如何使用观察者模式的

spring框架,学过java的同学一定对它不陌生,spring对于设计模式的应用十分值得我们学习,下面我们就来看一看它是如何使用观察者模式的。

spring中事件机制的底层原理就是观察者模式。java其实也有自己的事件机制,spring的事件机制也是由java的事件机制拓展而来。spring提供的事件机制是通过ApplicationEvent类和ApplicationListener接口实现的。我们仍然使用上面的天气信息发布的例子来讲解spring的观察者模式。

事件机制主要成员:

  1. 事件
    设计模式篇-观察者模式_第3张图片
    上图是spring提供的几类标准事件的类图,EventObject是jdk提供的事件,sping中的所有事件都要继承ApplicationEvent。事件比较好理解了,就是触发源,触发某个动作执行的源头。
    ContextStartedEvent:当应用上下文启动时发布;
    ContextClosedEvent:当应用上下文关闭时发布,“关闭”指所有的单实例bean都被销毁,不可以重启或者刷新;
    ContextStopedContext:当应用上下文停止时发布,“停止”指所有生命周期的bean都将接收到明确的停止信号,可以被重启;
    ContextRefreshEvent:当应用上下文初始化或者刷新时发布
    由上面的类图我们可以看出,spring支持两种事件,ApplicationContextEvent和PayloadApplicationEvent,至于这两种事件有什么区别,我们后面在讲解源码时再细细道来。
  2. 事件监听器(监听事件的触发,对其做出响应)
    设计模式篇-观察者模式_第4张图片spring中的事件监听器都必须实现ApplicationListen接口,它只定义了一个方法onApplicationEvent(E event),该方法接收ApplicationEvent事件对象,并实现响应事件的处理逻辑。关于SmartApplicationListener和GenericApplicationListener这里不做详细介绍。
    监听器也可以使用注解的方式实现@EventListener,因为这里主要是要讲原理,便不做介绍了。
  3. 事件广播器(负责将事件通知给监听器)
    设计模式篇-观察者模式_第5张图片
    当容器发布事件时,事件广播器会将事件通知给事件监听器注册表中的各个事件监听器。我们可以自定义广播器,如果没有自定义,spring则会使用默认的SimpleApplicationEventMulticaster广播器。接下来我们便使用spring的事件机制实现天气发布这个小例子,并讲解一下spring事件机制实现的方式。

使用spring事件机制实现天气发布例子

1、首先我们需要实现一个事件,发布事件,必须要有事件才行,这里定义是天气事件

/**
 * 天气事件,包含待发布的天气信息
 */
public class WeatherEvent extends ApplicationContextEvent {
private String weatherInfo;

public WeatherEvent(ApplicationContext source, String weatherInfo) {
    super(source);
    this.weatherInfo = weatherInfo;
}

public String getWeatherInfo() {
    return weatherInfo;
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

2、接下实现监听器,即天气监听者,我们实现3个监听者,我们仔细看一下有什么不同。
监听者1:

public class WeatherListen1 implements ApplicationListener<WeatherEvent> {
@Override
public void onApplicationEvent(WeatherEvent event) {
    System.out.println("天气监听者1:" + event.getWeatherInfo());
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

监听者2:

public class WeatherListen2 implements ApplicationListener<WeatherEvent> {
@Override
public void onApplicationEvent(WeatherEvent event) {
    System.out.println("天气监听者2:" + event.getWeatherInfo());
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

监听者3:

public class OtherListen3 implements ApplicationListener<PayloadApplicationEvent> {
@Override
public void onApplicationEvent(PayloadApplicationEvent event) {
    System.out.println("其它天气监听者3:" + event.getPayload().toString());
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

监听者1与监听者2监听ApplicationContextEvent事件,监听者3监听的是PayloadApplicatinEvent事件。这里我们看一下这两个事件的区别在哪。首先看一下AbstractApplicationContext这个抽象类的源码,当中有一个publishEvent方法,所有的事件都是在这里被发布的。我们所熟知的ClassPathXmlApplicationContext与FileSystemXmlApplicationContext类都是继承这个类,最终都是调用这个类的publishEvent方法发布事件的。
图中红色框框中可知,除了ApplicationEvent外的事件都被包装成PayloadApplicatinEvent事件。
设计模式篇-观察者模式_第6张图片PayloadApplicatinEvent类的构造函数有两个参数,第二个参数就是我们发布出去的事件。我们可以通过getPayload()获取发布的事件。
设计模式篇-观察者模式_第7张图片如果我们代码里有一个事件想发布,却又没有继承ApplicationEvent,为了不影响现有的功能,这时发布的事件就可以以PayloadApplicatinEvent的形式发布。
3、还有天气的发布者

/**
 * 这里也可以使用ApplicationEventPublisher,ApplicationContext接口继承了它,
 * 通过实现ApplicationContextAware获取应用上下文对象,并调用publishEvent方法发布事件
 */
public class WeatherPublisher implements ApplicationContextAware {
private ApplicationContext applicationContext;

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.applicationContext = applicationContext;
}

public void publishEvent(String weatherInfo) {
    WeatherEvent weatherEvent = new WeatherEvent(this.applicationContext, weatherInfo);
    //发布ApplicationContextEvent事件
    this.applicationContext.publishEvent(weatherEvent);
    //发布PayloadApplicationEvent事件
    this.applicationContext.publishEvent("今天天阴,请注意保暖!");
}

public static void main(String[] args) {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
    WeatherPublisher weatherPublisher = (WeatherPublisher) applicationContext.getBean("weatherPublisher");
    weatherPublisher.publishEvent("今天天气阳光明媚!");
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

运行结果:

天气监听者1:今天天气阳光明媚!
天气监听者2:今天天气阳光明媚!
其它天气监听者3:今天天阴,请注意保暖!

   
   
   
   
  • 1
  • 2
  • 3

事件的发布使用的是ApplicationContext,这个例子中的应用上下文是ClassPathXmlApplicationContext,讲到这里大家可能不太明白为什么使用应用上下文来发布事件,这个就需要我们来看看spring容器的源码了。究竟spring是如何形成这么一套完整的事件机制的。

解析spring事件机制的具体实现

spring在AbstractApplicationContext这个抽象类中实现了事件体系的搭建。AbstractApplicationContext的refresh()方法完成了spring容器的启动。事件机制的实现也是在这个过程中完成的。
设计模式篇-观察者模式_第8张图片
这里我们只看事件机制的实现过程,其他部分(beanFactory等)会在别的文章中详细介绍。关于事件机制的主要就是以上三个函数了。
1、首先,初始化应用上下文广播器,我们可以自定义事件广播器,只要实现ApplicationEventMulticaster这个接口即可。如果没有实现自己的广播器,spring便会使用默认的SimpleApplicationEventMulticaster作为事件广播器。
说了这么多,究竟什么是事件广播器呢?
事件广播器的作用是把Applicationcontext发布的Event广播给所有的在spring中注册的监听器。事件其实是通过事件广播器发布给监者的。
2、注册事件监听器
所有的事件监听器由spring管理,进入registerListeners()函数我们可以发现,监听器的管理者也是事件广播器。关于事件监听器我们不再详述。
3、完成容器初始化并广播ContextRefreshedEvent事件
finishRefresh()函数不再详细展开。

本文源码:码云地址

到这里关于观察者模式的介绍就结束了,第一次写博客,有很多不足的地方请见谅,不正确的地方还请及时指出,本文的源码会放在github上,大家可以下载观看,谢谢!

   
   
   
   
  • 1
                                

你可能感兴趣的:(设计模式篇-观察者模式)