java设计模式之观察者模式

天气预报需求

  1. 气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方)。
  2. 需要设计开放型API,便于其他第三方也能接入气象站获取数据。
  3. 提供温度、气压和湿度的接口
  4. 测量数据更新时,要能实时的通知给第三方

普通方案
java设计模式之观察者模式_第1张图片

  1. 通过getXxx方法,可以让第三方接入,并得到相关信息.
  2. 当数据有更新时,气象站通过调用dataChange() 去更新数据,当第三方再次获取时,就能得到最新数据,当然也可以推送。
public class CurrentConditions {
     
    private double temperature;//温度
    private double pressure;//气压
    private double humidity;//湿度
    public void updateData(double temperature,double pressure,double humidity){
     
        this.temperature=temperature;
        this.humidity=humidity;
        this.pressure=pressure;
        display();
    }

    public void display() {
     
        System.out.println("today temperature is"+this.temperature);
        System.out.println("today pressure is"+this.pressure);
        System.out.println("today humidity is"+this.humidity);
    }
}

public class WeatherData {
     
    private double temperature;//温度
    private double pressure;//气压
    private double humidity;//湿度
    private CurrentConditions currentConditions;

    public WeatherData(CurrentConditions currentConditions) {
     
        this.currentConditions = currentConditions;
    }

    public double getTemperature() {
     
        return temperature;
    }

    public double getPressure() {
     
        return pressure;
    }

    public double getHumidity() {
     
        return humidity;
    }
    public  void dataChange(){
     
     currentConditions.updateData(getTemperature(),getPressure(),getHumidity());
    }
    public void setData(double temperature,double pressure,double humidity){
     
        this.temperature=temperature;
        this.humidity=humidity;
        this.pressure=pressure;
        dataChange();
    }
}
public class Client {
     
    public static void main(String[] args) {
     
        CurrentConditions currentConditions = new CurrentConditions();
        WeatherData weatherData = new WeatherData(currentConditions);
        weatherData.setData(38,90,80);
    }
}

today temperature is38.0
today pressure is90.0
today humidity is80.0

问题分析

  1. 其他第三方接入气象站获取数据的问题
    //在WeatherData中,当增加一个第三方,都需要创建一个对应的第三方的公告板对象,并加入到dataChange, 不利于维护,也不是动态加入
  2. 无法在运行时动态的添加第三方 (新浪网站)
  3. 违反ocp原则=>观察者模式

观察者模式

观察者模式介绍

概念其实很简单,两个主体,一个观察者,一个被观察者,当被观察者发生变化时,观察者会有相应的动作。举几个例子,和我们日常生活息息相关的红绿灯,灯就相当于被观察者,行人就相当于观察者,当灯发生变化时,行人会有相应的动作:红灯停,绿灯行,黄灯亮了等一等。再比如我们现在玩的公众号,当我们订阅了某个公众号之后,公众号每发表一篇文章,就会向订阅了它的用户发送这篇文章,我们就可以浏览这篇文章了;当我们取消订阅了,它就不会再向我们推送这篇文章了;只要这个公众号一直在运行,就会一直有人订阅它或者取消订阅。这两个主体有个统一的称呼:被观察者成为主题(Subject),观察者仍是称为观察者(Observer)。

观察者模式还有很多其他的称谓,如发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

观察者模式原理
java设计模式之观察者模式_第2张图片
观察者模式类似上诉天气预报业务

  1. 气象局:Subject
  2. 用户/第三方网站:Observer

Subject:登记注册、移除和通知

  1. registerObserver 注册
  2. removeObserver 移除
  3. notifyObservers() 通知所有的注册的用户,根据不同需求,可以是更新数据,让用户来取,也可能是实时推送,看具体需求定

原理类图
java设计模式之观察者模式_第3张图片
角色分析

抽象主题(Subject):提供接口,可以增加和剔除观察者对象。一般用抽象类或者接口实现。

抽象观察者(Observer):提供接口,在得到主题的通知时更新自己。一般用抽象类或者接口实现。

具体主题(ConcreteSubject):将有关状态存入具体观察者,在具体主题的内部状态发生变化时,给所有注册过的观察者发出通知。一般是具体子类实现。

具体观察者(ConcreteObserver):存储与主题的状态自恰的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态 像协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用

在上述类图中,ConcreteSubject中有一个存储Observer的列表,这意味着ConcreteSubject并不需要知道引用了哪些ConcreteObserver,只要实现(继承)了Observer的对象都可以存到该列表中。在需要的时候调用Observer的update方法。

观察者模式解决天气预报问题
类图:
java设计模式之观察者模式_第4张图片
Subject

public interface Subject {
     
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}

Observer

public abstract class Observer {
     
    protected double temperature;//温度
    protected double pressure;//气压
    protected double humidity;//湿度
    public abstract  void update(double temperature,double pressure,double humidity);
    public abstract void display();

    public double getTemperature() {
     
        return temperature;
    }

    public void setTemperature(double temperature) {
     
        this.temperature = temperature;
    }

    public double getPressure() {
     
        return pressure;
    }

    public void setPressure(double pressure) {
     
        this.pressure = pressure;
    }

    public double getHumidity() {
     
        return humidity;
    }

    public void setHumidity(double humidity) {
     
        this.humidity = humidity;
    }
}

ConcreteSubject

public class WeatherDatas implements Subject{
     
    private double temperature;//温度
    private double pressure;//气压
    private double humidity;//湿度
    private List<Observer> observerList;

    public WeatherDatas() {
     
        this.observerList=new ArrayList<>();
    }

    public double getTemperature() {
     
        return temperature;
    }

    public double getPressure() {
     
        return pressure;
    }

    public double getHumidity() {
     
        return humidity;
    }
    public void setData(double temperature,double pressure,double humidity){
     
        this.temperature=temperature;
        this.humidity=humidity;
        this.pressure=pressure;
        dataChange();
    }

    public void dataChange() {
     
        notifyObservers();
    }

    @Override
    public void registerObserver(Observer observer) {
     
        observerList.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
     
        observerList.remove(observer);
    }

    @Override
    public void notifyObservers() {
     
        for (int i = 0; i <observerList.size() ; i++) {
     
            observerList.get(i).update(getTemperature(),getPressure(),getHumidity());
        }

    }
}

ConcreteObserver

public class weatherBureau extends Observer{
     

    @Override
    public void update(double temperature, double pressure, double humidity) {
     
        setHumidity(humidity);
        setPressure(pressure);
        setTemperature(temperature);
        display();
    }

    @Override
    public void display() {
     
        System.out.println("today temperature is "+getTemperature());
        System.out.println("today pressure is "+getPressure());
        System.out.println("today humidity is "+getHumidity());
    }
}


public class BaiduWeather extends Observer{
     

    @Override
    public void update(double temperature, double pressure, double humidity) {
     
        setHumidity(humidity);
        setPressure(pressure);
        setTemperature(temperature);
        display();
    }

    @Override
    public void display() {
     
        System.out.println("baidu prompt today temperature is "+getTemperature());
        System.out.println("baidu prompt today pressure is "+getPressure());
        System.out.println("baidu prompt today humidity is "+getHumidity());
    }
}
public class Tests {
     
    public static void main(String[] args) {
     
        WeatherDatas weatherDatas = new WeatherDatas();
        weatherBureau weatherBureau = new weatherBureau();
        BaiduWeather baiduWeather = new BaiduWeather();
        weatherDatas.registerObserver(weatherBureau);
        weatherDatas.registerObserver(baiduWeather);
        weatherDatas.setData(34,78,97);
    }
}

today temperature is 34.0
today pressure is 78.0
today humidity is 97.0
baidu prompt today temperature is 34.0
baidu prompt today pressure is 78.0
baidu prompt today humidity is 97.0

jdk实现

在Java语言的java.util包下,提供了一个Observable类以及一个Observer接口,构成Java语言对观察者模式的支持。
类图如下:
java设计模式之观察者模式_第5张图片

Observable:

package java.util;

/**
 * 抽象主题,用普通类实现
 */
public class Observable {
     
    private boolean changed = false;
    private Vector<Observer> obs;

    
    /**
     * 构建一个含有0个观察者的主题
     */
    public Observable() {
     
        obs = new Vector<>();
    }

    /**
     * 注册某个观察者到obs中
     */
    public synchronized void addObserver(Observer o) {
     
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
     
            obs.addElement(o);
        }
    }

    /**
     * 从obs中移除某个观察者
     */
    public synchronized void deleteObserver(Observer o) {
     
        obs.removeElement(o);
    }

    /**
     * 相当于notifyObservers(null),具体看下面那个
     */
    public void notifyObservers() {
     
        notifyObservers(null);
    }

    /**
     * 如果本对象有变化,则通知所有注册了的观察者,调用他们的update方法
     */
    public void notifyObservers(Object arg) {
     
        /*
         * a temporary array buffer, used as a snapshot of the state of
         * current Observers.
         */
        Object[] arrLocal;

        synchronized (this) {
     
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }

        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }

    /**
     * 清空obs
     */
    public synchronized void deleteObservers() {
     
        obs.removeAllElements();
    }

    /**
     * 将changed设置成true,标明本对象发生了变化
     */
    protected synchronized void setChanged() {
     
        changed = true;
    }

    /**
     * 将changed重置成false
     */
    protected synchronized void clearChanged() {
     
        changed = false;
    }

    /**
     * 检测本对象是否发生了变化
     */
    public synchronized boolean hasChanged() {
     
        return changed;
    }

    /**
     * 返回注册的观察者数量
     */
    public synchronized int countObservers() {
     
        return obs.size();
    }
}

Observer:

package java.util;

/**
 * 抽象观察者,接口实现
 */
public interface Observer {
     
    /**
     * 当被观察者对象发生改变时,此方法被调用
     */
    void update(Observable o, Object arg);
}

Watched:

package com.lee.jdkobserver;

import java.util.Observable;

/**
 * 具体主题
 */
public class Watched extends Observable {
     
    private String data = "";

    public void changeData(String data) {
     
        if (this.data.equals(data)) {
     
            return;
        }
        this.data = data;
        setChanged();
        notifyObservers(this.data);
    }
}

Watcher:

package com.lee.jdkobserver;

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

/**
 * 具体观察者,实现jdk中的Observer
 */
public class Watcher implements Observer {
     

    @Override
    public void update(Observable o, Object arg) {
     
        System.out.println("数据改变成了:" + arg);
    }
}

JdkObserverTest:

package com.lee.test;

import com.lee.jdkobserver.Watched;
import com.lee.jdkobserver.Watcher;

import java.util.Observer;

public class JdkObserverTest {
     

    public static void main(String[] args) {
     
        Watched watched = new Watched();
        Observer observer = new Watcher();
        watched.addObserver(observer);
        watched.changeData("first");
        watched.changeData("second");
        watched.changeData("third");
        watched.changeData("fourth");
    }
}

java设计模式之观察者模式_第6张图片

jdk事件

JDK 1.0及更早版本的事件模型基于职责链模式,但是这种模型不适用于复杂的系统,因此在JDK 1.1及以后的各个版本中,事件处理模型采用基于观察者模式的委派事件模型(DelegationEvent Model, DEM),即一个Java组件所引发的事件并不由引发事件的对象自己来负责处理,而是委派给独立的事件处理对象负责。这并不是说事件模型是基于Observer和Observable的,事件模型与Observer和Observable没有任何关系,Observer和Observable只是观察者模式的一种实现而已。

java中的事件机制的参与者有3种角色

Event Eource:事件源,发起事件的主体。
Event Object:事件状态对象,传递的信息载体,就好比Watcher的update方法的参数,可以是事件源本身,一般作为参数存在于listerner的方法之中。
Event Listener:事件监听器,当它监听到event object产生的时候,它就调用相应的方法,进行处理。

其实还有个东西比较重要:事件环境,在这个环境中,可以添加事件监听器,可以产生事件,可以触发事件监听器。

spring事件机制

spring的事件机制也是从java的事件机制拓展而来,具体往下看

ApplicationEvent:Spring中所有的事件父接口,继承自java的EventObject

package org.springframework.context;

import java.util.EventObject;

/**
 * Class to be extended by all application events. Abstract as it
 * doesn't make sense for generic events to be published directly.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 */
public abstract class ApplicationEvent extends EventObject {
     

    /** use serialVersionUID from Spring 1.2 for interoperability */
    private static final long serialVersionUID = 7099057708183571937L;

    /** System time when the event happened */
    private final long timestamp;


    /**
     * Create a new ApplicationEvent.
     * @param source the object on which the event initially occurred (never {@code null})
     */
    public ApplicationEvent(Object source) {
     
        super(source);
        this.timestamp = System.currentTimeMillis();
    }


    /**
     * Return the system time in milliseconds when the event happened.
     */
    public final long getTimestamp() {
     
        return this.timestamp;
    }

}

ApplicationListener:spring中所有的事件监听器父接口,继承自java的EventListener

package org.springframework.context;

import java.util.EventListener;

/**
 * Interface to be implemented by application event listeners.
 * Based on the standard {@code java.util.EventListener} interface
 * for the Observer design pattern.
 *
 * 

As of Spring 3.0, an ApplicationListener can generically declare the event type * that it is interested in. When registered with a Spring ApplicationContext, events * will be filtered accordingly, with the listener getting invoked for matching event * objects only. * * @author Rod Johnson * @author Juergen Hoeller * @param the specific ApplicationEvent subclass to listen to * @see org.springframework.context.event.ApplicationEventMulticaster */ @FunctionalInterface public interface ApplicationListener<E extends ApplicationEvent> extends EventListener { /** * Handle an application event. * @param event the event to respond to */ void onApplicationEvent(E event); }

使用 spring 事件机制重新实现示例

@Data
public class WechatNotice extends ApplicationEvent {
     
    private String publisher;
    private String articleName;

    public WechatNotice(Object source, String publisher, String articleName) {
     
        super(source);
        this.publisher = publisher;
        this.articleName = articleName;
    }
}

public class WeChatClient implements ApplicationListener {
     
    private String username;

    public WeChatClient(String username) {
     
        this.username = username;
    }

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
     
        if (event instanceof WechatNotice) {
     
            WechatNotice notice = (WechatNotice) event;
            System.out.println(String.format("用户<%s> 接收到 <%s>微信公众号 的推送,文章标题为 <%s>", username, notice.getPublisher(), notice.getArticleName()));
        }
    }

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

public class WeChatAccounts implements ApplicationContextAware {
     
    private ApplicationContext ctx;
    private String name;

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

    public void setName(String name) {
     
        this.name = name;
    }

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

    public void publishArticles(String articleName, String content) {
     
        System.out.println(String.format("\n<%s>微信公众号 发布了一篇推送,文章名称为 <%s>,内容为 <%s> ", this.name, articleName, content));
        ctx.publishEvent(new WechatNotice(this.name, this.name, articleName));
    }
}

在 resources 目录下创建 spring.xml 文件,填入下面的内容

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="WeChatAccounts" class="com.observer.sprintevent.WeChatAccounts" scope="prototype">
        <constructor-arg name="name" value=""></constructor-arg>
    </bean>
    <bean id="WeChatClient1" class="com.observer.sprintevent.WeChatClient">
        <constructor-arg name="username" value="张三"></constructor-arg>
    </bean>
    <bean id="WeChatClient2" class="com.observer.sprintevent.WeChatClient">
        <constructor-arg name="username" value="李四"></constructor-arg>
    </bean>
    <bean id="WeChatClient3" class="com.observer.sprintevent.WeChatClient">
        <constructor-arg name="username" value="王五"></constructor-arg>
    </bean>
</beans>

测试:

public class Test {
     
    public static void main(String[] args) {
     
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");

        WeChatAccounts accounts = (WeChatAccounts) context.getBean("WeChatAccounts");
        accounts.setName("活跃的咸鱼");
        accounts.setApplicationContext(context);

        accounts.publishArticles("设计模式 | 观察者模式及典型应用", "观察者模式的内容...");
    }
}

<活跃的咸鱼>微信公众号 发布了一篇推送,文章名称为 <设计模式 | 观察者模式及典型应用>,内容为 <观察者模式的内容...> 
用户<张三> 接收到 <活跃的咸鱼>微信公众号 的推送,文章标题为 <设计模式 | 观察者模式及典型应用>
用户<李四> 接收到 <活跃的咸鱼>微信公众号 的推送,文章标题为 <设计模式 | 观察者模式及典型应用>
用户<王五> 接收到 <活跃的咸鱼>微信公众号 的推送,文章标题为 <设计模式 | 观察者模式及典型应用>

在此示例中 ApplicationContext 对象的实际类型为 ClassPathXmlApplicationContext,其中的与 publishEvent 方法相关的主要代码如下:

private ApplicationEventMulticaster applicationEventMulticaster;

public void publishEvent(ApplicationEvent event) {
     
    this.getApplicationEventMulticaster().multicastEvent(event);
    if (this.parent != null) {
     
        this.parent.publishEvent(event);
    }
}

ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException {
     
    return this.applicationEventMulticaster;
}

protected void initApplicationEventMulticaster() {
     
        ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
        if (beanFactory.containsLocalBean("applicationEventMulticaster")) {
     
            this.applicationEventMulticaster = (ApplicationEventMulticaster)beanFactory.getBean("applicationEventMulticaster", ApplicationEventMulticaster.class);
        } else {
     
            this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
            beanFactory.registerSingleton("applicationEventMulticaster", this.applicationEventMulticaster);
        }

    }

其中的 SimpleApplicationEventMulticaster 如下,multicastEvent 方法主要是通过遍历 ApplicationListener(注册由 AbstractApplicationEventMulticaster 实现),使用线程池框架 Executor 来并发执行 ApplicationListener 的 onApplicationEvent 方法,与示例本质上是一致的

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
     
    private Executor taskExecutor;

    public void multicastEvent(final ApplicationEvent event) {
     
        Iterator var2 = this.getApplicationListeners(event).iterator();

        while(var2.hasNext()) {
     
            final ApplicationListener listener = (ApplicationListener)var2.next();
            Executor executor = this.getTaskExecutor();
            if (executor != null) {
     
                executor.execute(new Runnable() {
     
                    public void run() {
     
                        listener.onApplicationEvent(event);
                    }
                });
            } else {
     
                listener.onApplicationEvent(event);
            }
        }

    }
}

观察者模式优缺点及应用场景

优点

  1. 主题与观察者建立一个抽象的耦合而不是紧密的耦合,降低了耦合度;主题只需要维护一个抽象观察者的集合,无需了解具体观察者,使得可以有各种各样不同的观察者实现。

  2. 支持广播通信,主题会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度。

  3. 符合“开闭原则”,增加新的具体观察者无须修改原有代码,可拓展性高。

  4. 观察者模式在观察目标和观察者之间建立一个抽象的耦合。观察目标只需要维持一个抽象观察者的集合,无须了解其具体观察者。由于观察目标和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。

缺点
5. 如果主题有很多直接或者间接观察者,那么全部通知到会很耗时。

  1. 主题与观察者之间如果存在循环依赖,可能导致系统崩溃。
  2. 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

观察者模式应用场景

抽象的来讲:对一个对象状态的更新,需要其他对象同步更新,而且其他对象的数量动态可变;对象仅需要将自己的更新通知给其他对象而不需要知道其他对象的细节。大家可以根据以上两点作为基本准则,某个场景是否满足观察者模式。
 
 具体应用场景就有很多了,比如文中的事件机制、公众号订阅,tomcat源码中也有很多地方用到了(有兴趣的兄弟可以去找找)。

参考
小旋风:https://blog.csdn.net/wwwdc1012/article/details/83317973
韩顺平老师:Java设计模式

你可能感兴趣的:(设计模式,设计模式,java)