栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

设计模式3 观察者模式

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

设计模式3 观察者模式

定义

观察者模式(Observer Pattern)定义了一种一对多的依赖关系,这样一来,当一个对象改变状态时,它的所有依赖都会受到通知并自动更新。
——《Head FIRST 设计模式》

观察者模式是JDK中使用最多的模式之一。
AKA:发布-订阅模式(Publish/Subscribe)

主要角色
  • 被观察者 Observable/主题Subject:抽象主题角色把所有对观察者对象的引用保存在一个集合(比如ArrayList、Vector)里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
  • 具体的被观察者 Concreate Observable:将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。
  • 观察者 Observer:为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。
  • 具体的观察者 Concreate Observer:存储与被观察者对应的状态。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用。

如下图(图源:程序员小灰):

举例

源自《Head FIRST设计模式》实现气象站。
气象站提供有温度、湿度、气压等气象数据,如果数据发生了更新,需要随时跟踪获取这些数据,并且display出来。

首先建立接口

//被观察者接口
public interface Subject {
    void addObserver(Observer observer);
    void removerObserver(Observer observer);
    void notifyAllObservers();
}
public interface Observer {
    void update(float temp, float humidity);
}
public interface DisplayElement {
    void display();
}

实现被观察的WeatherData

public class WeatherData implements Subject{
    private float temperature;
    private float humidity;
    private List observerList;
    public WeatherData() {
        observerList = new ArrayList();
    }
    public void changeWeatherData(float temp,float humidity){
        this.temperature = temp;
        this.humidity = humidity;
        notifyAllObservers();
    }
    @Override
    public void addObserver(Observer observer) {
        observerList.add(observer);
    }
    @Override
    public void removerObserver(Observer observer) {
        int index = observerList.indexOf(observer);
        if(index >= 0)
            observerList.remove(index);
    }
    @Override
    public void notifyAllObservers() {
        for(Observer obs: observerList){
            obs.update(temperature,humidity);
        }
    }
}

实现天气布告板,他作为观察者观察者WeatherData

public class CurrentConditionsDisplay implements Observer,DisplayElement{
    private float temperature;
    private float humidity;
    private String displayName;
    //此处保留了对Subject的引用
    private Subject weatherData;

    public CurrentConditionsDisplay(String displayName, Subject weatherData) {
        this.displayName = displayName;
        this.weatherData = weatherData;
        weatherData.addObserver(this);
    }
    @Override
    public void update(float temp, float humidity) {
        this.temperature = temp;
        this.humidity = humidity;
        System.out.println(displayName + "气象布告栏:当前气温" + temperature + "℃,湿度:" + humidity + "%");
    }
  //Getter&Setter忽略
}

这里保留了对Subject的引用,在实践中是可以有所变化的。
这里的subject接口可以更改为抽象类,在类中定义一些通用的方法。实际上,Java内置的观察者模式就是需要继承自一个抽象类。下面会详细介绍Java内置的观察者模式。

推拉模型下的观察者模式

在上面这种代码运行下,当weather变化时,会将变化通知给观察者。也就是主题对象向观察者推送主题的详细信息,不管观察者是否需要。这种方式是推模型。
但是存在一些需求,观察者想去主动的拉取一些数据下来,而不是被动的被喂数据。先看代码

拉模型下的例子

还是这个例子,如果将逻辑改为拉模式,可以这样修改:
在WeatherData中,不再保留对Subject的引用,改为由参数传入
首先Observer修改接口参数,直接将主题对象(Subject)传入:

public interface Observer {
	//注意之前的参数为具体的温度湿度数值,现在是Subject
    void update(Subject subject);
}

那么在调用时,需要传入一个Subject的实例

public class WeatherData implements Subject{
    private float temperature;
    private float humidity;
    private List observerList;
    public WeatherData() {
        observerList = new ArrayList();
    }
    public void changeWeatherData(float temp,float humidity){
        this.temperature = temp;
        this.humidity = humidity;
        notifyAllObservers();
    }
    @Override
    public void addObserver(Observer observer) {
        observerList.add(observer);
    }
    @Override
    public void removerObserver(Observer observer) {
        int index = observerList.indexOf(observer);
        if(index >= 0)
            observerList.remove(index);
    }
    @Override
    public void notifyAllObservers() {
        for(Observer obs: observerList){
            obs.update(this);
        }
    }
}

实现void update(Subject)

public class CurrentConditionsDisplay implements Observer,DisplayElement{
    private float temperature;
    private float humidity;
    private String displayName;
    //此处保留了对Subject的引用
    private Subject weatherData;

    public CurrentConditionsDisplay(String displayName, Subject weatherData) {
        this.displayName = displayName;
        this.weatherData = weatherData;
        weatherData.addObserver(this);
    }
    @Override
    public void update(Subject subject) {
        this.temperature = ((WeatherData)subject).temperature;
        this.humidity = ((WeatherData)subject).humidity;
        System.out.println(displayName + "气象布告栏:当前气温" + temperature + "℃,湿度:" + humidity + "%");
    }
  //Getter&Setter忽略
}
推拉模型对比

部分参考Java设计模式の观察者模式(推拉模型)

  1. 推模型是假定主题对象知道观察者需要的数据;而拉模型是主题对象不知道观察者具体需要什么数据,没有办法的情况下,干脆把自身传递给观察者,让观察者自己去按需要取值。
  2. 推模型可能会使得观察者对象难以复用,因为观察者的update()方法是按需要定义的参数,以后随着业务的变化这个方法可能不再适用;拉模型update()方法的参数是主题对象本身,这基本上是主题对象能传递的最大数据集合了,基本上可以适应各种情况的需要,但是需要将主题对象完全暴露给使用者。
Java内置观察者模式

Java本身有自带实现观察者模式。在Java语言的java.util库里面,提供了一个Observable类以及一个Observer接口。

Observer接口

JDK8文档Interface Observer
JDK9已弃用Interface Observer
这个接口只定义了一个方法,即update()方法,当被观察者对象的状态发生变化时,被观察者对象的notifyObservers()方法就会调用这一方法。

package java.util;


public interface Observer {

     *
     * @param   o     the observable object 被观察/主题对象
     * @param   arg   an argument passed to the notifyObservers method.传递给notifyObservers的参数          
     */
    void update(Observable o, Object arg);
}
Observable类

JDK8文档Class Observable
JDK9已弃用Class Observable
被观察者类都是java.util.Observable类的子类。
核心函数有两个:

  • setChanged() :被调用之后会设置一个内部标记变量,代表被观察者对象的状态发生了变化。
  • notifyObservers():这个方法被调用时,会调用所有登记过的观察者对象的update()方法,使这些观察者对象可以更新自己。
package java.util;


public class Observable {
    private boolean changed = false;
    //Vector线程安全
    private Vector obs;

    

    public Observable() {
        obs = new Vector<>();
    }

    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }
	
    public void notifyObservers() {
        notifyObservers(null);
    }
	//通知观察者
    public void notifyObservers(Object arg) {
        
        Object[] arrLocal;
		//如果changed标记是true,就把所有观察者放进一个数组,并把changed标记置为false
        synchronized (this) {
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }
		//通知所有数组里的观察者去update信息
        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }

    public synchronized void deleteObservers() {
        obs.removeAllElements();
    }

	//这里是protected,
    protected synchronized void setChanged() {
        changed = true;
    }

    protected synchronized void clearChanged() {
        changed = false;
    }

    public synchronized boolean hasChanged() {
        return changed;
    }

    public synchronized int countObservers() {
        return obs.size();
    }
}

在使用时,一个简单的例子实现被观察者

public class Watched extends Observable {
    private String state = "";

    public String getState() {
        return state;
    }
	//在setter里面通知所有观察者,通过setChanged()函数和notifyObservers()函数
    public void setState(String state) {
        if (!this.state.equals(state)) {
            this.state = state;
            setChanged();
        }
        notifyObservers();
    }
}
缺点

Observable是一个类,而Java又不支持多继承。
在JDK9中已弃用,原因是支持Observer和支持的事件模型Observable 非常有限,发送通知的顺序 Observable不明确,状态变化与通知不是一一对应的。

参考

Java设计模式の观察者模式(推拉模型)
观察者设计模式(二) - 推拉模式
漫画:什么是“观察者模式”?
简说设计模式——观察者模式

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/323072.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号