带你入门设计模式-观察者模式

本文深入讲解观察者模式,包括其定义、Java内置实现及其局限性,以及如何在Spring框架中运用这一模式。通过实例演示了自定义观察者模式和Spring事件机制的实现。

目录

定义

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

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

java内置观察者模式实现

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

  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. 定义天气预告订阅者小王和小李,需要实现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());
        }
    }

}

天气订阅者小李:

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());
        }
    }

}

运行结果如下:

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

其实上面的例子是很简单的,打开Observable类的源码仔细阅读就能明白。这里我们贴一下Observable类的伪代码:
Observable类的伪代码
从这里能看出来,为什么我们在通知前必须要调用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();
}
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. 天气预告订阅者
public interface Observer {
    public void update(String wetherInfo);
}
public class XiaoLiObserver implements Observer {
    public void update(String wetherInfo) {
        System.out.println("小李收到天气信息:" + wetherInfo);
    }
}
public class XiaoWangObserver implements Observer {
    public void update(String wetherInfo) {
        System.out.println("小王收到天气信息:" + wetherInfo);
    }
}

运行结果如下:

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

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

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

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

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

事件机制主要成员:

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

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

public class WeatherListen1 implements ApplicationListener<WeatherEvent> {

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

}

监听者2:

public class WeatherListen2 implements ApplicationListener<WeatherEvent> {

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

}

监听者3:

public class OtherListen3 implements ApplicationListener<PayloadApplicationEvent> {

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

监听者1与监听者2监听ApplicationContextEvent事件,监听者3监听的是PayloadApplicatinEvent事件。这里我们看一下这两个事件的区别在哪。首先看一下AbstractApplicationContext这个抽象类的源码,当中有一个publishEvent方法,所有的事件都是在这里被发布的。我们所熟知的ClassPathXmlApplicationContext与FileSystemXmlApplicationContext类都是继承这个类,最终都是调用这个类的publishEvent方法发布事件的。
图中红色框框中可知,除了ApplicationEvent外的事件都被包装成PayloadApplicatinEvent事件。
AbstractApplicationContext源码PayloadApplicatinEvent类的构造函数有两个参数,第二个参数就是我们发布出去的事件。我们可以通过getPayload()获取发布的事件。
PayloadApplicationEvent源码如果我们代码里有一个事件想发布,却又没有继承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:今天天阴,请注意保暖!

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

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

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

本文源码:码云地址

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值