Observer Pattern 观察者模式


观察者模式概述:

定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

就像报纸和杂志的订阅:
1、报社的业务就是出版报纸,这相当于观察者模式里的主题,主题里的状态发生变化就相当于有了新闻,报社的任务是出 版报纸,而主题的任务就是发出变化的通知。


2、向某家报社订阅报纸,只要它们有新报纸出版,就会给你送来。只要你是它们的用户,你就会一直收到报纸。这相当于 观察者模式里的观察者,一旦主题发生变化并发出通知,那么所有依赖该主题(观察了该主题)的观察者都会收到通知。

3、当你不想再看报纸的时候,取消订阅,就不会再收到他们的报纸。这就相当于某个观察者取消观察了该主题,那么主题 再发生变化的时候发出变化的通知的时候该观察者就不会再收到通知。

4、只要报社还在运营,就会有人一直向他们订阅报纸或者取消订阅报纸。


观察者模式的结构:

观察者模式里的主题必须有所有的观察者的引用,这样才可以发通知或者传送数据给所有的观察者。但是如果多出了一个观察者就要在主题里添加一个观察者的引用,那么就会一直去改原有的代码,而且还会破坏代码的封装性。所以可以设计一个接口,让所有的观察者都去实现该接口,借口里有一个方法供主题去调用来通知所有的观察者数据发生了变化。这样主题只需持有这个观察者接口的引用即可。

有些新类型的观察者出现时,主题的代码不需要修改。假如我们有个新的具体类需要当观察者,我们不需要为了兼容新类型而修改主题的代码,所有需要做的就是在新类里实现Observer接口,然后注册成为观察者就哦了。主题不在乎别的,它只会发送通知给所有实现了Observer接口并且注册了该主题的观察者。


主题接口:

<span style="font-size:14px;">package cn.wang;

public interface Subject {
	void registObserver(Observer o);//注册到主题
	void unregistObserver(Observer o);//取消注册
	void notifyObservers();//通知所有观察者数据发生了变化
}</span>

观察者接口:

<span style="font-size:14px;">package cn.wang;

public interface Observer {
	void update(String temp,String humidity,String pressure);
}</span>


具体的主题,实现主题接口:

<span style="font-size:14px;">package cn.wang;

import java.util.ArrayList;
import java.util.List;

public class WeatherDataSubject implements Subject {

	private float temp;
	private float humidity;
	private float pressure;
	private List<Observer> observers;

	/**
	 * 主题的构造方法,我们在构造方法里维护一个Observer观察者集合
	 */
	public WeatherDataSubject() {
		observers = new ArrayList<Observer>();
	}

	@Override
	// 注册观察者
	public void registObserver(Observer o) {
		observers.add(o);
	}

	@Override
	// 取消注册观察者
	public void unregistObserver(Observer o) {
		if (observers.contains(o)) {
			observers.remove(o);
		}
	}

	@Override
	public void notifyObservers() {
		for (Observer o : observers) {
			//这里将float的数据转换成String
			o.update(String.valueOf(temp), String.valueOf(humidity), String.valueOf(pressure));
		}
	}

	/**
	 * 气象台在数据变化的时候会调用该方法,不用管它是怎么被调用的,可能是传感器检测到温度变化了就会调用它
	 */
	private void dataSetChanged() {
		// 数据发生了变化,就通知所有的观察者
		notifyObservers();
	}

	public void setData(float temp, float humididy, float pressure) {
		this.temp = temp;
		this.humidity = humididy;
		this.pressure = pressure;
		// 数据发生变化调用dataSetChanged方法
		dataSetChanged();
	}

}</span>


温度数据的观察者:

<span style="font-size:14px;">package cn.wang;

public class TemperatureObserver implements Observer {

	private Subject subject;//这里生成了主题的引用是为了以后可以取消注册。

	/**
	 * 观察者实现了Observer接口之后要注册到主题,在这里我们在构造函数里进行注册
	 */
	public TemperatureObserver(Subject subject) {
		//这里也可以注册其他的主题,只要相应的主题实现了Subject接口即可
		this.subject = subject;
		subject.registObserver(this);
	}

	@Override
	public void update(String temp, String humidity, String pressure) {
		// 这种方式是推的形式,每个观察者选择自己需要的数据来进行操作
		System.out.println("当前温度是: " + temp);
	}

}</span>


湿度数据的观察者:

<span style="font-size:14px;">package cn.wang;

public class HumityObserver implements Observer {

	private Subject subject;

	public HumityObserver(Subject subject) {
		this.subject = subject;
		subject.registObserver(this);
	}

	@Override
	public void update(String temp, String humidity, String pressure) {
		// 这种方式是推的形式,每个观察者选择自己需要的数据来进行操作
		System.out.println("当前的湿度是: " + humidity);
	}

}</span>

气压数据的观察者:

<span style="font-size:14px;">package cn.wang;

public class PressuerObserver implements Observer {

	private Subject subject;

	public PressuerObserver(Subject subject) {
		this.subject = subject;
		subject.registObserver(this);
	}

	@Override
	public void update(String temp, String humidity, String pressure) {
		// 这种方式是推的形式,每个观察者选择自己需要的数据来进行操作
		System.out.println("当前的气压是: " + pressure);
	}

}</span>

测试类:

<span style="font-size:14px;">package cn.wang;

public class Test {

	public static void main(String[] args) {
		// 实例化一个气象站
		WeatherDataSubject s = new WeatherDataSubject();

		// 实例化几个观察者
		Observer tempObserver = new TemperatureObserver(s);
		Observer humidityObserver = new HumityObserver(s);
		Observer pressuerObserver = new PressuerObserver(s);
		
		//改变主题的数据
		s.setData(1.0f, 0.5f, 4.3f);
		
		s.setData(2.0f, 0.6f, 5.2f);
	}
}</span>

输出结果是:

<span style="font-size:14px;">当前温度是: 1.0
当前的湿度是: 0.5
当前的气压是: 4.3
当前温度是: 2.0
当前的湿度是: 0.6
当前的气压是: 5.2</span>


观察者模式松耦合:

我们可以独立地复用主题和观察者。如果我们需要在别的地方使用这些主题或者观察者,可以轻易的复用,因为这两者并不是紧耦合。

改变主题或观察者的其中一方,并不会影响另一方。因为两者是松耦合的,所以只要它们之间的接口仍被遵守,我们就可以自由滴改变它们。其实我们编码为了后期更好的可扩展和可维护性,应该为了交互对象之间的松耦合设计而努力。松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降到了最低。


Java API里的观察者模式:

Java API里已经封装了装饰者模式,在java.util包下有一个Observable抽象类,这就相当于我们的Subject主题接口;有一个Observer接口,这就相当于我们自己写的观察者需要实现的观察者接口。

Observable和Observer接口用起来很方便,因为许多功能都已经事先实现好了。你甚至可以用推或者拉的方式来获取变化的数据。

java API里观察者的类图:



java API里的主题,Observable:

使用的时候我们要写一个自己的主题来继承Observable类,同时会继承addObserver方法和deleteObserver方法,这两个方法分别是注册该主题和取消注册该主题。还会继承一个notifyObservers方法,这个方法会在数据发生变化的时候被调用,这个方法会调用所有观察者的update方法去更新观察者的数据。另外java API还为我们提供了一个setChanged方法,这个方法主要是为了更精确的来确定数据是否发生了变化,例如:如果温度变化了0.01°,那么主题就会通知所有的观察者数据发生了变化,但是这种情况不是我们想要的,我们有可能需要在温度变化了0.5°的时候再通知观察者,这时就可以在通知所有观察者之前进行判断,如果温度的变化确实超过了0.5°,那么就先调用setChanged方法,确定数据确实发生了变化,这个时候才会调用观察者的update方法,否则如果条件不满足0.5°,也就是没有调用setChanged方法,那么即使数据发生了变化也不会通知观察者。

java API里的观察者,Observer接口:

观察者就需要去实现Observer接口,同时会取实现update方法。这就跟我们自己写的观察者接口一样了。


具体用法:

另外还有一个不一样的地方就是java的API同时支持推数据和拉数据。所谓的推数据就是指主题的数据发生变化的时候主题会调用观察者的updae方法,并把所有变化的数据一股脑推送给所有的观察者,不管这些观察者用不用得到这些数据,这种方式调用的方法是notifyObserver(Object args)方法,其中参数就是变化的数据。拉数据则是指主题调用观察者的update方法的时候把自己的引用传过去,这样观察者就可以在update方法里根据自己的业务去拉主题的数据,这种方式下主题需要有getter方法来获取对象中的数据,这种方式调用的是notifyObservers()方法。具体的做法如下:

主题

<span style="font-size:14px;">package com.wang;

import java.util.Observable;

public class WeatherDataSubject extends Observable {
	private float temp;
	private float humidity;
	private float pressure;
	
	/**
	 * 气象台在数据变化的时候会调用该方法,不用管它是怎么被调用的,可能是传感器检测到温度变化了就会调用它
	 */
	private void dataSetChanged() {
		// 数据发生了变化,就通知所有的观察者
		//在通知所有观察者之前要先调用这个方法
		setChanged();
		notifyObservers();//如果不传参,就是拉的形式
	}

	public void setData(float temp, float humididy, float pressure) {
		this.temp = temp;
		this.humidity = humididy;
		this.pressure = pressure;
		// 数据发生变化调用dataSetChanged方法
		dataSetChanged();
	}
	
	//给各个属性设置一个getter方法,方便观察者拉数据
	public float getTemp() {
		return temp;
	}
	public float getHumidity() {
		return humidity;
	}
	public float getPressure() {
		return pressure;
	}
}</span>

温度观察者:

<span style="font-size:14px;">package com.wang;

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

public class TemperatureObserver implements Observer {

	private Observable o;

	/**
	 * 注册到Observable的具体类
	 * @param 被观察者,Observable的子类
	 */
	public TemperatureObserver(Observable o) {
		this.o = o;
		o.addObserver(this);
	}

	@Override
	public void update(Observable o, Object arg) {
		if (o instanceof WeatherDataSubject) {
			// 拉数据
			System.out.println("当前的温度是: " + ((WeatherDataSubject) o).getTemp());
		}
	}

}</span>

湿度观察者:

<span style="font-size:14px;">package com.wang;

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

public class HumidityObserver implements Observer {

	private Observable o;

	public HumidityObserver(Observable o) {
		this.o = o;
		o.addObserver(this);
	}

	@Override
	public void update(Observable o, Object arg) {
		if(o instanceof WeatherDataSubject){
			System.out.println("当前的湿度是: "+((WeatherDataSubject)o).getHumidity());
		}
	}

}</span>


气压观察者:

<span style="font-size:14px;">package com.wang;

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

public class PressureObserver implements Observer {

	private Observable o;

	public PressureObserver(Observable o) {
		this.o = o;
		o.addObserver(this);
	}

	@Override
	public void update(Observable o, Object arg) {
		if(o instanceof WeatherDataSubject){
			System.out.println("当前的气压是: "+((WeatherDataSubject)o).getPressure());
		}
	}

}</span>

测试类:


<span style="font-size:14px;">package com.wang;

import java.util.Observer;

public class Test {

	public static void main(String[] args) {
		// 实例化一个主题
		WeatherDataSubject subject = new WeatherDataSubject();

		// 实例化温度观察者
		Observer tempObserver = new TemperatureObserver(subject);

		// 实例化湿度观察者
		Observer humidityObserver = new HumidityObserver(subject);

		// 实例化气压观察者
		Observer pressureObserver = new PressureObserver(subject);
		
		//改变主题的数据
		subject.setData(1.0f, 0.5f, 2.3f);
		System.out.println("-----------------------------");
		subject.setData(1.2f, 0.8f, 3.2f);
	}
}
</span>

结果:

<span style="font-size:14px;">当前的气压是: 2.3
当前的湿度是: 0.5
当前的温度是: 1.0
-----------------------------
当前的气压是: 3.2
当前的湿度是: 0.8
当前的温度是: 1.2</span>



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章