观察者模式的定义:
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
主题是一个接口,“观察”也是一个接口,“观察接口”是“主题接口”的方法中的参数,是利用多态 将 “观察者的实例” 传入主题者的“方法参数”中的。人人皆可主题者,人人皆可观察者,实现接口就行。观察者换个名字,叫订阅者会容易理解很多,就像订阅报纸一样。
“主题者的类定义” 和 “观察者的类定义” 之间的通信主要是通过,主题接口和观察接口,进行。即是通过第三者接口实现了松耦合。
核心就是,都是操作接口的方法,而不是直接操作类。由于接口的抽象方法引起具体实现类的方法的执行,类是被动的。
示例需求:
一个气象站,写一个应用软件来显示三个传感器探测到的气象信息,气象站通过传感器获取天气信息,所以你要建造的软件就是从三个传感器中获取信息,并且通过三个用户界面(布告板)把天气信息展示出来。天气信息主要是三种,当前状况,气象统计,天气预报。
需求分析:
现在气象局告诉你,你要定义一个WeatherData类,且该类中有measurementsChanged()方法,我一旦有气象信息更新,就会自动调用你的measurementsChanged()方法了。
所以你可以从该类中的measurementsChanged()方法中获取各个气象站的气象信息,先别管measurementsChanged()方法是怎么被自动调用的,可以是在java的mian方法中人工被调用行不行?然后你要做的就是,如何通过在measurementsChanged()方法中通知三个用户界面(布告板),气象信息更新了。
模式的概念:
出版者+订阅者 = 观察者模式 , 只是这里在模式的术语上,出版者称作“主题(subject)”, 订阅者称作“观察者(Observer)”,也就是说WeatherData是一个主题者,三块布告板是三个观察者。
模式的巧妙之处:
在于观察者是如何注册成为主题的观察者的,也就是如何订阅主题的。观察者是将主题者的一个实例作为参数,传入到观察者的注册方法中。或者另一种方式,观察者将自身实例作为一个参数,传入到主题者的注册方法中。所以,全局环境必须要有一个主题者实例。
注意,是主题者的实例,不是定义,是已经实现了的。主题者一般定义为一个接口,这样人人都可以成为主题者,只要遵循主题的接口,实现主题接口里面的方法就可以了。
注册的方法一般都是,观察者将自身实例作为一个参数,传入到主题者的注册方法中。所以主题接口在设计的时候,就需要把相关方法的参数设置为观察者接口,没错,观察者一般也是一个接口。实际运行代码的时候,是利用多态将观察者的实例传入主题者的方法参数中的。
所以就是(主题者和观察者的)接口、接口,类定义、类定义,实例、实例。
主题者负责维护观察者的数组,也就是每注册一个观察者,主题者就把观察者的引用保存到自己的数组中,删除同理。
主题接口只使用观察接口的抽象方法。
观察者的类定义中用变量维护了主题接口的引用,利用多态,用于注册、取消订阅等操作。(这里只是定义,真正的执行都是main方法中的主题者实例和观察者实例)。
这么说的话,“主题者的类定义”是利用 “抽象方法的参数” 维护了观察者接口的引用。
★ 所以说,主题者发布某一个通知,就是调用了主题接口里的“主题抽象方法”,而“主题抽象方法”的参数又是观察者接口,然后主题者的类定义中 “主题抽象方法”的 “具体实现方法” 就可以通过这个参数 任意调用 “观察者接口中的抽象方法”,所以“具体观察者类的 具体实现方法”必然就被调用了,因为具体类必须实现接口的抽象方法嘛。所以总得来看,主题者实例的某方法的执行,就会链式地引起观察者实例的某方法的执行。也就是发布了通知了。其实画图出来一目了然,自己画去。
示例代码:
主题者的接口、类定义、(实例在main方法中):
package s_Interface;
/**
* 用于提供所有想成为主题者都必须实现的接口
* @author mathew
*
*/
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}
package s_ImpolementInterfaceClass;
import java.util.ArrayList;
import s_Interface.Observer;
import s_Interface.Subject;
/**
* 主题者,主要工作是发布通知,提供方法给观察者注册、移除。实际就是main方法中调用主题者实例的方法咯。
*
*/
public class WeatherData implements Subject {
private ArrayList observers; //观察者数组,主题的观察者们容身之地
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
// TODO Auto-generated constructor stub
observers = new ArrayList();
}
@Override
public void registerObserver(Observer o) {
// TODO Auto-generated method stub
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
// TODO Auto-generated method stub
int i = observers.indexOf(o);
if (i >= 0) {
observers.remove(i);
}
}
/**
* 主题者会调用observer接口的的upadate方法,而upadte方法看是谁实现,如果是在当前类中实现的,那就调用当前类实现的upadate方法。
* 也就是利用了多态,利用了观察类实现了observer接口,而主题者只维护observer接口的抽象对象,而不维护observer接口的具体实现的对象。
* 然后,主题者就发通知给observer接口各个抽象对象们,发通知的形式就是调用observer接口的update方法
*/
@Override
public void notifyObservers() {
// TODO Auto-generated method stub
for (int i = 0; i < observers.size();i++) {
Observer observer = (Observer) observers.get(i);
observer.update(temperature, humidity, pressure);
}
}
//当测量数据发生变化时,就通知观察者
public void measurementsChanged() {
notifyObservers();
}
//为了模拟气象站传感器检测获取到的数据,现在只好人工设置了咯
public void setMeasurements(float temperature, float humidity,float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}
观察者的接口、类定义、(实例在main方法中):
package s_Interface;
/**
* 该接口用于提供观察者和主题的共同接口,用于松耦合时彼此之间通过第三方进行沟通
*
*/
public interface Observer {
public void update(float temp, float humidity,float pressure);
}
package s_ImpolementInterfaceClass;
import s_Interface.*;
/**
* 观察者,该类是一个展示当前天气情况的展示板,自己并非主题者,但是内部维护了一个主题者的引用,等待主题者发布通知后。对通知的内容进行处理
* @author mathew
*
*/
public class CurrentConditionsDisplay implements DisplayElement, Observer {
private float temperature;
private float humidity;
private Subject weatherData;//主题者,其实是保留了主题者的引用
//构造函数,初始化时,获取主题者的引用,并负责维护该引用
public CurrentConditionsDisplay(Subject weatherData) {
// TODO Auto-generated constructor stub
this.weatherData = weatherData;
this.weatherData.registerObserver(this); //自己注册成为观察者,其实更好的设计不是在构造方法中,而是抽取出来别的方法
}
//观察者在这里具体实现公共接口的方法,因为主题者会调用公共接口的抽象方法,由于多态,所以就是调用了下面的方法实现代码了
@Override
public void update(float temperature, float humidity, float pressure) {
// TODO Auto-generated method stub
this.temperature = temperature;
this.humidity = humidity;
display();
}
@Override
public void display() {
// TODO Auto-generated method stub
System.out.println("Current conditons:" + temperature + " F degress, " + humidity + "% humidity" );
System.out.println("亲,打印了CurrentConditionDisplay的信息了哟!!!!!!");
}
}
主题者、观察者的实例,即是main方法:
package s_Class;
import s_ImpolementInterfaceClass.*;
/**
* 那就用气象站对象来展示该模式吧,气象站赋值给WeatherData对象,然后响应的观察者就会有相应的动作了
* @author mathew
*
*/
public class WeatherStation {
public static void main(String[] args) {
//主题者对象,因为各个布告板都是观察这个天气信息的
WeatherData weatherData = new WeatherData();
//观察者对象,在构造方法中注册了主题,其实就是通过主题的引用,把自己添加在主题里的数组里了,也就是注册了。
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
System.out.println("开启执行了啊!!");
weatherData.setMeasurements(81, 65, 30.5f);
weatherData.setMeasurements(89, 63, 31.5f);
weatherData.setMeasurements(85, 66, 32.6f);
}
}