观察者模式

1. 天气预报项目需求

天气预报项目需求,具体要求如下:

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

传统方式解决天气预报需求

WeatherData

  1. 通过对气象站项目的分析,我们可以初步设计出一个WeatherData
  2. 通过getXxx()方法,可以让第三方接入,并得到相关信息
  3. 当数据有更新时,气象站通过调用dataChange()去更新数据,当第三方再次获取时,就能得到最新数据,当然也可以推送

1609167933167

推送方式:

CurrentConditions(当前的天气情况)可以理解成是接入的第三方,在 WeatherData 类中的 dataChange() 方法中,调用 CurrentConditions 的 update() 方法,来更新 CurrentConditions(第三方) 中的天气数据

1609167961699

代码实现:

  1. 气象网站的显示数据

    // 显示当前天气情况(可以理解成是气象站自己的网站)
    public class CurrentConditions {
    	// 温度,气压,湿度
    	private float temperature;
    	private float pressure;
    	private float humidity;
    
    	// 更新 天气情况,是由 WeatherData 来调用,我使用推送模式
    	public void update(float temperature, float pressure, float humidity) {
    		this.temperature = temperature;
    		this.pressure = pressure;
    		this.humidity = humidity;
    		display();
    	}
    
    	// 显示
    	public void display() {
    		System.out.println("***Today mTemperature: " + temperature + "***");
    		System.out.println("***Today mPressure: " + pressure + "***");
    		System.out.println("***Today mHumidity: " + humidity + "***");
    	}
    }
    
    
    
  2. 气象站的后台数据,可以将数据实时的更新到网站上

    /**
     * 1. 包含最新的天气情况信息 
     * 2. 含有 CurrentConditions 对象
     * 3. 当数据有更新时,就主动的调用   CurrentConditions对象update方法(含 display), 这样他们(接入方)就看到最新的信息
     *
     */
    public class WeatherData {
    	private float temperatrue;
    	private float pressure;
    	private float humidity;
    	private CurrentConditions currentConditions;
    	
    	public WeatherData(CurrentConditions currentConditions) {
    		this.currentConditions = currentConditions;
    	}
    
    	public float getTemperature() {
    		return temperatrue;
    	}
    
    	public float getPressure() {
    		return pressure;
    	}
    
    	public float getHumidity() {
    		return humidity;
    	}
    
    	public void dataChange() {
    		// 调用 接入方的 update
    		currentConditions.update(getTemperature(), getPressure(), getHumidity());
    	}
    
    	// 当数据有更新时,就调用 setData
    	public void setData(float temperature, float pressure, float humidity) {
    		this.temperatrue = temperature;
    		this.pressure = pressure;
    		this.humidity = humidity;
    		// 调用dataChange, 将最新的信息 推送给 接入方 currentConditions
    		dataChange();
    	}
    }
    
    
  3. 测试代码

    public class Client {
    	public static void main(String[] args) {
    		// 创建接入方 currentConditions
    		CurrentConditions currentConditions = new CurrentConditions();
    		// 创建 WeatherData 并将 接入方 currentConditions 传递到 WeatherData中
    		WeatherData weatherData = new WeatherData(currentConditions);
    
    		// 更新天气情况
    		weatherData.setData(30, 150, 40);
    
    		// 天气情况变化
    		System.out.println("============天气情况变化=============");
    		weatherData.setData(40, 160, 20);
    
    	}
    }
    
    

传统问题分析

  1. 其他第三方接入气象站获取数据的问题
  2. 无法在运行时动态的添加第三方 (新浪网站)
  3. 再添加新的第三方时,需要修改 WeatherData 类,违反ocp原则 --> 观察者模式

2. 观察者模式介绍

观察者模式类似订牛奶业务

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

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

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

Observer:接收输入


观察者模式:对象之间多对一依赖的一种设计方案,被依赖的对象为Subject,依赖的对象为Observer, Subject通知Observer变化,比如这里的奶站是Subject,是1的一方。用户时Observer,是多的一方

3. 观察者模式解决天气预报需求

类图:

1609168167030

代码实现

  1. 观察者的抽象接口,定义了如何更新观察者天气信息的规范抽象方法

    //观察者接口,有观察者来实现
    public interface Observer {
    
    	public void update(float temperature, float pressure, float humidity);
    
    }
    
    
  2. 具体的观察者,实现了 update() 方法的业务逻辑 (气象台本站)

    public class CurrentConditions implements Observer {
    
    	// 温度,气压,湿度
    	private float temperature;
    	private float pressure;
    	private float humidity;
    
    	// 更新 天气情况,是由 WeatherData 来调用,我使用推送模式
    	public void update(float temperature, float pressure, float humidity) {
    		this.temperature = temperature;
    		this.pressure = pressure;
    		this.humidity = humidity;
    		display();
    	}
    
    	// 显示
    	public void display() {
    		System.out.println("***Today mTemperature: " + temperature + "***");
    		System.out.println("***Today mPressure: " + pressure + "***");
    		System.out.println("***Today mHumidity: " + humidity + "***");
    	}
    }
    
    
  3. 具体的观察者,实现了 update() 方法的业务逻辑 (百度)

    public class BaiduSite implements Observer {
    
    	// 温度,气压,湿度
    	private float temperature;
    	private float pressure;
    	private float humidity;
    
    	// 更新 天气情况,是由 WeatherData 来调用,我使用推送模式
    	public void update(float temperature, float pressure, float humidity) {
    		this.temperature = temperature;
    		this.pressure = pressure;
    		this.humidity = humidity;
    		display();
    	}
    
    	// 显示
    	public void display() {
    		System.out.println("===百度网站====");
    		System.out.println("***百度网站 气温 : " + temperature + "***");
    		System.out.println("***百度网站 气压: " + pressure + "***");
    		System.out.println("***百度网站 湿度: " + humidity + "***");
    	}
    
    }
    
    
  4. 主题的抽象类,定义了操作观察者的规范,有添加、移除、通知操作者,通过调用 registerObserver() 方法订阅该主题

    //接口, 让WeatherData 来实现 
    public interface Subject {
    
    	public void registerObserver(Observer o);
    
    	public void removeObserver(Observer o);
    
    	public void notifyObservers();
    	
    }
    
    
  5. 主题的具体实现类,聚合了一个 ArrayList<Observer> 对象,用于存储观察者集合,并实现了 Subject 接口中定义的抽象方法

    /**
     * 类是核心
     * 1. 包含最新的天气情况信息 
     * 2. 含有 观察者集合,使用ArrayList管理
     * 3. 当数据有更新时,就主动通知所有的(接入方)就看到最新的信息
     * @author Administrator
     *
     */
    public class WeatherData implements Subject {
    	private float temperatrue;
    	private float pressure;
    	private float humidity;
    	// 观察者集合
    	private ArrayList<Observer> observers;
    
    	public WeatherData() {
    		observers = new ArrayList<Observer>();
    	}
    
    	public float getTemperature() {
    		return temperatrue;
    	}
    
    	public float getPressure() {
    		return pressure;
    	}
    
    	public float getHumidity() {
    		return humidity;
    	}
    
    	// 当数据有更新时,就调用 setData
    	public void setData(float temperature, float pressure, float humidity) {
    		this.temperatrue = temperature;
    		this.pressure = pressure;
    		this.humidity = humidity;
    		// 调用notifyObservers, 将最新的信息 推送给观察者
    		notifyObservers();
    	}
    
    	// 注册一个观察者
    	@Override
    	public void registerObserver(Observer o) {
    		observers.add(o);
    	}
    
    	// 移除一个观察者
    	@Override
    	public void removeObserver(Observer o) {
    		if (observers.contains(o)) {
    			observers.remove(o);
    		}
    	}
    
    	// 遍历所有的观察者,并通知
    	@Override
    	public void notifyObservers() {
    		for (int i = 0; i < observers.size(); i++) {
    			observers.get(i).update(this.temperatrue, this.pressure, this.humidity);
    		}
    	}
    }
    
    
  6. 测试代码

    public class Client {
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		// 创建一个WeatherData
    		WeatherData weatherData = new WeatherData();
    
    		// 创建观察者
    		CurrentConditions currentConditions = new CurrentConditions();
    		BaiduSite baiduSite = new BaiduSite();
    
    		// 注册到weatherData
    		weatherData.registerObserver(currentConditions);
    		weatherData.registerObserver(baiduSite);
    
    		// 测试
    		System.out.println("通知各个注册的观察者, 看看信息");
    		weatherData.setData(10f, 100f, 30.3f);
    
    		weatherData.removeObserver(currentConditions);
    		// 测试
    		System.out.println();
    		System.out.println("通知各个注册的观察者, 看看信息");
    		weatherData.setData(10f, 100f, 30.3f);
    	}
    
    }
    
    

观察者模式的好处

  1. 观察者模式设计后, 会以集合的方式来管理用户(Observer), 包括注册, 移除和通知。
  2. 这样, 我们增加观察者(这里可以理解成一个新的公告板), 就不需要去修改核心类 WeatherData,遵守了 ocp 原则

4. Jdk Observable 源码分析

  1. Observable 的作用和地位等价于 我们前面讲过 Subject
  2. Observable 是类,不是接口,类中已经实现了核心的方法,即管理Observer的方法 add()delete()notify()
  3. Observer 的作用和地位等价于我们前面讲过的 Observer,定义了抽象方法 update()
  4. ObservableObserver 的使用方法和前面讲过的一样,只是Observable 是类,通过继承来管理观察者,实现观察者模式

Observable 类:

public class Observable {
    private boolean changed = false;
    private Vector<Observer> 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;

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

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

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

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

Observer 定义了抽象方法update(), 主题方法通过调用观察者的update() 方法将主题推送至具体的观察者

public interface Observer {
    void update(Observable o, Object arg);
}

posted @ 2021-01-08 18:03  哈哈丶丶  阅读(51)  评论(0编辑  收藏  举报