您的位置:首页 > 编程语言 > Java开发

Java设计模式--观察者模式

2017-11-09 14:35 295 查看

观察者模式

参考《Head First设计模式》中的观察者模式完成。

气象监测应用需求

观察者模式介绍

手写观察者模式

Java内置的观察者模式

气象监测应用需求

根据气象站实时输出的湿度、温度和气压值制作三块布告板。第一块布告板实时显示当前的温度、湿度和气压;第二块布告板显示当日的平均温度、最低温度以及最高温度;第三块布告板根据天气显示预报信息。

气象站提供了WeatherData类来获得实时测量的温度、湿度和气压值。



气象站提供的接口如上如所示,三个getter方法用于获取温度、湿度和气压;每当气象测量值变化,就会调用measurementsChanged()方法,实现measurementsChanged()就是我们的工作。

分析:

考虑到需求中布告板显示的内容可能会发生变化,为了方便日后修改程序,希望每次只需要添加一块新的布告板,而其他程序不需要变化,努力做到交互对象之间松耦合。这个需求中,天气测量值一旦变化,三个布告板要及时获得改变并展示,完全符合观察者模式。

观察者模式介绍

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

观察者模式中有主题和观察者两个对象,以报纸的订阅为例简述观察者模式。

报社的任务就是出版报纸;

向某家报社订阅报纸,只要有新报纸出版,报社就会给你送来。只要你订阅了该报纸,就会一直收到新报纸;

当你不想看该报纸时,取消订阅,他们就不会再送报纸来;

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

把订阅报纸类比为观察者模式,出版者就是“主题”,订阅者就是“观察者”。在该需求中,气象站就是一个“主题”,而每块布告板就是一个“观察者”。每个布告板(观察者)向气象站(主题)注册,就可以在测量值变化时获得消息;取消某布告板类比为“观察者”向“主题”注销;新增一块布告板类比为新增一个气象“主题”的“观察者”。

手写观察者模式

面向对象的设计原则:针对接口编程,不针对实现编程。



根据上述原则,把主题和观察者分别抽象为Subject和Observer接口。并添加一个DisplayElement接口用于展示。

Observer接口中只有一个update()方法,用于当主题变化时执行。

Subject接口中有使得观察者订阅的方法registerObserver();当观察者不想接收信息时的取消订阅方法removeObserver();以及当主题数据发生改变时通知订阅该主题的所有观察者的notifyObserver()方法。实际的主题,例如本需求中的WeatherData需要实现该接口,内部维护一个观察者数组。registerObserver()方法中只需要把观察者加入自身的观察者数组;removeObserver()方法中把要取消订阅的观察者移除;notifyObserver()则是遍历当前的观察者数组,依次调用每个观察者的update()即可。

Subject接口

/**
* Created by Janet on 2017/11/6.
* 观察者模式中主题的接口
*/
public interface Subject {
public void registerObserver(Observer o);//观察者o订阅主题
public void removeObserver(Observer o);//观察者o取消订阅主题
public void notifyObserver();//主题通知观察者
}


Observer接口

/**
* Created by Janet on 2017/11/6.
* 观察者模式中观察者的接口
*/
public interface Observer {
public void update(float temp,float humidity,float pressure);//主题传来通知
}


用于展示的接口

/**
* Created by Janet on 2017/11/9.
* 展示结果的接口
*/
public interface DisplayElement {
public void display();
}


WeatherData主题

import java.util.ArrayList;

/**
* Created by Janet on 2017/11/6.
* 气象预报的主题
*/
public class WeatherData implements Subject {

private ArrayList observers;//观察者列表
private float temperature;//温度
private float humidity;//湿度
private float pressure;//气压

//每次设置温湿度和气压,主题都会发生改变
public void setTemperature(float temperature,float humidity,float pressure){
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}

public WeatherData(){
observers = new ArrayList();
}

//主题发生改变时执行的方法
public void measurementsChanged(){
notifyObserver();
}

//观察者o订阅主题
@Override
public void registerObserver(Observer o) {
observers.add(o);
}

//观察者o取消订阅主题
@Override
public void removeObserver(Observer o) {
int index = observers.indexOf(o);
if( index >= 0 ){
observers.remove(o);
}
}

//主题通知所有订阅者
@Override
public void notifyObserver() {
for(int i = 0;i<observers.size();i++){
Observer o = (Observer) observers.get(i);
o.update(temperature,humidity,pressure);
}
}
}


观察者1–展示温度湿度气压的布告板

/**
* Created by Janet on 2017/11/6.
* 第一块展示温湿度的布告板
*/
public class CurrentConditionDisplay implements Observer,DisplayElement {

private Subject weatherData;
private float temperature;
private float humidity;

//一创建就向主题注册
public CurrentConditionDisplay(Subject weatherData){
this.weatherData = weatherData;
weatherData.registerObserver(this);
}

@Override
public void display() {
System.out.println("Current condition : "+temperature+"F degrees and "+humidity+"% humidity");
}

//主题变化时执行的函数
@Override
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
display();
}
}


观察者2–展示温度最大最小及平均值的布告板

/**
* Created by Janet on 2017/11/6.
* 第二块展示最小,平均,最大温度的布告板
*/
public class StatisticsDisplay implements Observer,DisplayElement {

private float minTemperature = Float.MAX_VALUE;//最小温度
private float avgTemperature;//平均温度
private float maxTemperature = Float.MIN_VALUE;//最大温度
private int num = 0;//用于计算平均温度
private Subject weatherData;

//创建观察者时要向主题注册
public StatisticsDisplay(Subject weatherData){
this.weatherData = weatherData;
weatherData.registerObserver(this);
}

@Override
public void display() {
System.out.println("Avg/Max/Min temperature = "+avgTemperature+"/"+maxTemperature+"/"+minTemperature);
}

//主题变化时执行的函数
@Override
public void update(float temp, float humidity, float pressure) {

if( minTemperature > temp ){
minTemperature = temp;
}
if( maxTemperature < temp ){
maxTemperature = temp;
}
avgTemperature = (avgTemperature * num + temp)/(num+1);
num++;
display();
}
}


测试函数

/**
* Created by Janet on 2017/11/6.
*/
public class WeatherStation {
public static void main(String[] args){
WeatherData weatherData = new WeatherData();
CurrentConditionDisplay o1 = new CurrentConditionDisplay(weatherData);//第一块布告板
StatisticsDisplay o2 = new StatisticsDisplay(weatherData);
weatherData.setTemperature(19,20,1000);
weatherData.setTemperature(13,15,1500);
weatherData.setTemperature(0,22,1000);
}
}


执行结果如下:



Java内置的观察者模式

java.util包中的Observable是主题的超类(注意,java内置的主题是类不是接口),Observer是观察者的接口。



实际主题需要继承超类Observeable,已经写好了观察者订阅,取消订阅以及通知的方法。其中setChanged()方法用于当主题数据修改时,用来标记状态已经改变。WeatherData内部的notifyObservers()方法会首先判断标志位是否更改,再通知各观察者。

观察者获取主题变化的数据实际上有“推”和“拉”两种方式。“推”表示主题数据发生变化时,主动把变化的数据推送给订阅的观察者们;“拉”表示当观察者需要时主动向主题索取数据。上文中我们只是自己实现了主题“推数据”的方法。Java内置的观察者模式支持“推”和“拉”两种获取数据的模式。

利用Java内置实现WeatherData

注意,使用Java内置主题时,notifyObservers()有两种重载方法:

notifyObservers()和notifyObservers(Object arg);notifyObservers(Object arg)可以传送指定数据给观察者。notifyObservers()用于拉数据的模式,notifyObservers(Object arg)用于推的模式。

import java.util.Observable;

/**
* Created by Janet on 2017/11/9.
* 使用java内置类实现主题
*/
public class WeatherData extends Observable{//继承Observable,内部实现了主题的创建观察者列表等方法
private float temperature;
private float humidity;
private float pressure;

public WeatherData(){}//此处无需自行创建观察者列表,超类已经创建

public void setMeasurements(float temperature,float humidity,float pressure){
this.pressure = pressure;
this.humidity = humidity;
this.temperature = temperature;
measurementsChanged();//数据改变后调用该方法
}

private void measurementsChanged() {
setChanged();//设置改变标志位
notifyObservers();//没有参数传入,说明是观察者向主题索取数据
}

public float getTemperature(){
return temperature;
}
public float getHumidity(){
return humidity;
}
public float getPressure(){
return pressure;
}

}


利用Java内置实现观察者

注意,Java内置的观察者获取主题动态有“推”和“拉”两种模式。推即为主题

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

/**
* Created by Janet on 2017/11/9.
* 使用java内置类实现观察者
*/
public class CurrentConditionsDisplay implements Observer,DisplayElement {
private Observable observable;//观察者内部记录主题
private float temperature;
private float humidity;

//构造函数中把观察者加入到主题中
public CurrentConditionsDisplay(Observable observable){
this.observable = observable;
observable.addObserver(this);//主题把观察者加入到列表
}

@Override
public void display() {
System.out.println("Current conditions: "+temperature+" F degrees and "+humidity+"% humidity");
}

//不同的观察者索取的数据由update方法展示
@Override
public void update(Observable o, Object arg) {
if( o instanceof WeatherData ){
WeatherData weatherData = (WeatherData) o;
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
display();
}
}
}


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

/**
* Created by Janet on 2017/11/9.
*/
public class StatisticsDisplay implements Observer,DisplayElement {
private Observable observable;
private float minTemperature = Float.MAX_VALUE;//最小温度
private float avgTemperature;//平均温度
private float maxTemperature = Float.MIN_VALUE;//最大温度
private int num = 0;//用于计算平均温度

public StatisticsDisplay(Observable observable){
this.observable = observable;
observable.addObserver(this);
}
@Override
public void display() {
System.out.println("Avg/Max/Min temperature = "+avgTemperature+"/"+maxTemperature+"/"+minTemperature);
}

@Override
public void update(Observable o, Object arg) {
if( o instanceof WeatherData ){
WeatherData weatherData = (WeatherData) o;
float temp = weatherData.getTemperature();
if( minTemperature > temp ){
minTemperature = temp;
}
if( maxTemperature < temp ){
maxTemperature = temp;
}
avgTemperature = (avgTemperature * num + temp)/(num+1);
num++;
display();
}
}
}


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

/**
* Created by Janet on 2017/11/9.
*/
public class ForecastDisplay implements Observer,DisplayElement {
private Observable observable;//主题
private float currentPressure = 29.92f;
private float lastPressure;

public ForecastDisplay(Observable observable){
this.observable = observable;
observable.addObserver(this);
}

@Override
public void display() {
if( this.currentPressure < this.lastPressure ){
System.out.println("气压变小");
}else if( this.currentPressure > this.lastPressure ){
System.out.println("气压变大");
}else{
System.out.println("气压不变");
}

}

@Override
public void update(Observable o, Object arg) {
if( o instanceof WeatherData ){
WeatherData weatherData = (WeatherData) o;
this.lastPressure = currentPressure;
this.currentPressure = weatherData.getPressure();
display();
}
}
}


测试函数

/**
* Created by Janet on 2017/11/9.
*/
public class WeatherStation {
public static void main(String[] args){
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
weatherData.setMeasurements(19,20,1000);
weatherData.setMeasurements(13,15,1500);
weatherData.setMeasurements(0,22,1000);
}
}


运行结果如下:



Java内置和手写观察者模式的区别

Java内置的主题采用类的形式,扩展性不如接口;

Java内置观察者创建的顺序不等同于主题改变时通知的顺序,在上例子中可见,而自写的主题内部维护观察者数组采用有序的ArrayList,可以保证顺序。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java设计模式