本篇博客主要对设计模式中的观察者模式进行介绍
观察者模式定义:
定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
UML类图:
松耦合的威力
当两个对象之间松耦合,它们依然可以交互,但是不太清楚彼此的细节。
观察者模式提供了一种对象设计,让主题和观察者之间松耦合。
关于观察者的一些,主题只知道观察者实现了某个接口(也就是Observer
接口)。主题不需要知道观察者的具体类是谁、做了些什么或其他任何细节。
任何时候我们都可以增加新的观察者。因为主题唯一依赖的东西是一个实现Observer
接口的对象列表,所以我们可以随时增加观察者。事实上,在运行时我们可以用新的观察者取代现有的观察者,主题不会受到任何影响。同样的,也可以在任何时候删除某些观察者。
在新类型的观察者出现时,主题的代码不需要修改。假如我们有个新的具体类需要当观察者,我们不需要为了兼容新类型而修改主题的代码,所有要做的就是在新的类里实现此观察者接口,然后注册为观察者即可。主题不在乎别的,它只会发送通知给所有实现了观察者接口的对象。
我们可以独立地复用主题或观察者。如果我们在其他地方需要使用主题或观察者,可以轻易地复用,因为二者并非紧耦合。
改变主题或观察者其中一方,并不会影响另一方。因为两者是松耦合的,所以只要他们之间的接口仍被最受,我们就可以自由地改变他们。
实现气象站
气象站设计类图:
定义一个主题接口,其中接口中的registerObserver()
和removeObserver()
这两个方法都需要一个观察者作为变量,该观察者是用来注册或被删除的。当主题状态改变时,notifyObservers()
方法会被调用,以通知所有的观察者。
public interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
在观察者Observer
接口中,当气象观测值改变时,主题会把这些状态值当做update()
方法的参数,传递给观察者。所有的观察者都必须实现update()
方法,以实现观察者接口。
public interface Observer {
void update(float temp, float humidity, float pressure);
}
DisplayElement
接口只包含了一个方法,也就是display()
。当布告板需要显示时,调用此方法。
public interface DisplayElement {
void display();
}
weatherData
类实现了Subject
接口,该类定义了一个observers
来记录观察者,此ArrayList
是在构造器中建立的;在调用registerObserver()
方法注册成为观察者的时候,只要把传入的Observer
对象加入到observers
中即可;同样,当观察者想取消注册的时候,把它从observers
中删除即可;在notifyObservers
中,把主题的状态告诉了每一个观察者。因为观察者都实现了update()
方法,所以知道如何通知它们;measurementsChanged()
方法表示,当从气象站获取更新的观测值时,调用通知方法;setMeasurements()
方法中模拟了从气象站获观测值的过程。
public class WeatherData implements Subject {
private ArrayList<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
observers = new ArrayList<Observer>();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
int i = observers.indexOf(o);
if (i >= 0) {
observers.remove(i);
}
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
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();
}
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}
目前状况布告板,此布告板实现了Observer
接口,所以可以从WeatherData
对象中获得改变,同时实现了DisplayElement
接口用来实现显示功能;构造器需要weatherData
对象作为注册之用;当update()
被调用时,我们把温度和湿度保存起来,然后调用display()
方法显示出来。
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private Subject weatherData;
public CurrentConditionsDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}
@Override
public void display() {
System.out.println("Current conditions: " + temperature
+ "F degrees and " + humidity + "% humidity");
}
}
统计布告板
public class StatisticsDisplay implements Observer, DisplayElement {
private float maxTemp = 0.0f;
private float minTemp = 200;
private float tempSum= 0.0f;
private int numReadings;
private WeatherData weatherData;
public StatisticsDisplay(WeatherData weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void update(float temp, float humidity, float pressure) {
tempSum += temp;
numReadings++;
if (temp > maxTemp) {
maxTemp = temp;
}
if (temp < minTemp) {
minTemp = temp;
}
display();
}
@Override
public void display() {
System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings)
+ "/" + maxTemp + "/" + minTemp);
}
}
预测布告板
public class ForecastDisplay implements Observer, DisplayElement {
private float currentPressure = 29.92f;
private float lastPressure;
private WeatherData weatherData;
public ForecastDisplay(WeatherData weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void update(float temp, float humidity, float pressure) {
lastPressure = currentPressure;
currentPressure = pressure;
display();
}
@Override
public void display() {
System.out.print("Forecast: ");
if (currentPressure > lastPressure) {
System.out.println("Improving weather on the way!");
} else if (currentPressure == lastPressure) {
System.out.println("More of the same");
} else if (currentPressure < lastPressure) {
System.out.println("Watch out for cooler, rainy weather");
}
}
}
酷热指数布告板
public class HeatIndexDisplay implements Observer, DisplayElement {
float heatIndex = 0.0f;
private WeatherData weatherData;
public HeatIndexDisplay(WeatherData weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void update(float t, float rh, float pressure) {
heatIndex = computeHeatIndex(t, rh);
display();
}
private float computeHeatIndex(float t, float rh) {
float index = (float)((16.923 + (0.185212 * t) + (5.37941 * rh) - (0.100254 * t * rh)
+ (0.00941695 * (t * t)) + (0.00728898 * (rh * rh))
+ (0.000345372 * (t * t * rh)) - (0.000814971 * (t * rh * rh)) +
(0.0000102102 * (t * t * rh * rh)) - (0.000038646 * (t * t * t)) + (0.0000291583 *
(rh * rh * rh)) + (0.00000142721 * (t * t * t * rh)) +
(0.000000197483 * (t * rh * rh * rh)) - (0.0000000218429 * (t * t * t * rh * rh)) +
0.000000000843296 * (t * t * rh * rh * rh)) -
(0.0000000000481975 * (t * t * t * rh * rh * rh)));
return index;
}
@Override
public void display() {
System.out.println("Heat index is " + heatIndex);
}
}
测试四个布告板,首先建立一个WeatherData
对象,建立四个布告板,并把WeatherData
对象传给它们,最后调用weatherData
的setMeasurements
方法模拟新的气象测量。
public class WeatherStationHeatIndex {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
HeatIndexDisplay heatIndexDisplay = new HeatIndexDisplay(weatherData);
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 29.2f);
}
}
执行结果如下图。
Java内置的观察者模式
使用Java内置的观察者模式实现气象站
重写WeatherData类,实现java.util.Observable
类;使用Java内置观察者模式实现时,不需要追踪观察者,也不需要管理注册与删除(让超类代劳即可),所以我们把注册、添加、通知的相关代码删除;构造器不再需要为了记住观察者们而建立数据结构;在调用notifyObservers()
之前,需要调用setChanged()
来指示状态已经改变。
import java.util.Observable;
public class WeatherData extends Observable {
private float temperature;
private float humidity;
private float pressure;
public WeatherData() { }
public void measurementsChanged() {
setChanged();
notifyObservers();
}
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}
显示接口
public interface DisplayElement {
public void display();
}
目前状况布告板,实现java.utilObserver
接口,构造器需要一个Observable
当参数,并将CurrentConditionsDisplay
对象等级成为观察者;改变update()
方法,增加Observable
和数据对象作为参数;在update()
中,先确定可观察者属于WeatherData
类型,然后利用setter
方法获取温度和湿度测量值,最火调用display()
。
import java.util.Observable;
import java.util.Observer;
public class CurrentConditionsDisplay implements Observer, DisplayElement {
Observable observable;
private float temperature;
private float humidity;
public CurrentConditionsDisplay(Observable observable) {
this.observable = observable;
observable.addObserver(this);
}
@Override
public void update(Observable obs, Object arg) {
if (obs instanceof WeatherData) {
WeatherData weatherData = (WeatherData)obs;
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
display();
}
}
@Override
public void display() {
System.out.println("Current conditions: " + temperature
+ "F degrees and " + humidity + "% humidity");
}
}
统计布告板
import java.util.Observable;
import java.util.Observer;
public class StatisticsDisplay implements Observer, DisplayElement {
private float maxTemp = 0.0f;
private float minTemp = 200;
private float tempSum= 0.0f;
private int numReadings;
public StatisticsDisplay(Observable observable) {
observable.addObserver(this);
}
@Override
public void update(Observable observable, Object arg) {
if (observable instanceof WeatherData) {
WeatherData weatherData = (WeatherData)observable;
float temp = weatherData.getTemperature();
tempSum += temp;
numReadings++;
if (temp > maxTemp) {
maxTemp = temp;
}
if (temp < minTemp) {
minTemp = temp;
}
display();
}
}
@Override
public void display() {
System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings)
+ "/" + maxTemp + "/" + minTemp);
}
}
预测布告板
import java.util.Observable;
import java.util.Observer;
public class ForecastDisplay implements Observer, DisplayElement {
private float currentPressure = 29.92f;
private float lastPressure;
public ForecastDisplay(Observable observable) {
observable.addObserver(this);
}
@Override
public void update(Observable observable, Object arg) {
if (observable instanceof WeatherData) {
WeatherData weatherData = (WeatherData)observable;
lastPressure = currentPressure;
currentPressure = weatherData.getPressure();
display();
}
}
@Override
public void display() {
System.out.print("Forecast: ");
if (currentPressure > lastPressure) {
System.out.println("Improving weather on the way!");
} else if (currentPressure == lastPressure) {
System.out.println("More of the same");
} else if (currentPressure < lastPressure) {
System.out.println("Watch out for cooler, rainy weather");
}
}
}
酷热指数布告板
import java.util.Observable;
import java.util.Observer;
public class HeatIndexDisplay implements Observer, DisplayElement {
float heatIndex = 0.0f;
public HeatIndexDisplay(Observable observable) {
observable.addObserver(this);
}
@Override
public void update(Observable observable, Object arg) {
if (observable instanceof WeatherData) {
WeatherData weatherData = (WeatherData)observable;
float t = weatherData.getTemperature();
float rh = weatherData.getHumidity();
heatIndex = (float)
(
(16.923 + (0.185212 * t)) +
(5.37941 * rh) -
(0.100254 * t * rh) +
(0.00941695 * (t * t)) +
(0.00728898 * (rh * rh)) +
(0.000345372 * (t * t * rh)) -
(0.000814971 * (t * rh * rh)) +
(0.0000102102 * (t * t * rh * rh)) -
(0.000038646 * (t * t * t)) +
(0.0000291583 * (rh * rh * rh)) +
(0.00000142721 * (t * t * t * rh)) +
(0.000000197483 * (t * rh * rh * rh)) -
(0.0000000218429 * (t * t * t * rh * rh)) +
(0.000000000843296 * (t * t * rh * rh * rh)) -
(0.0000000000481975 * (t * t * t * rh * rh * rh)));
display();
}
}
@Override
public void display() {
System.out.println("Heat index is " + heatIndex);
}
}
测试布告板类
public class WeatherStationHeatIndex {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentConditions = new CurrentConditionsDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
HeatIndexDisplay heatIndexDisplay = new HeatIndexDisplay(weatherData);
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 29.2f);
}
}
运行结果
java.util.Observable的黑暗面
可观察者是一个“类”而不是一个“接口”接口,更糟糕的是,它甚至没有实现一个接口。不幸的是,java.util.Observable
的实现有许多问题,限制了它的使用和复用。
Observable是一个类
因为Observable
是一个“类”,你必须设计一个类继承它。如果某类相同具有Observable
类和另一个超类的行为,就会陷入两难,毕竟Java不支持多重继承。这限制了Observable
的复用潜力。
Observable将关键的方法保护起来
如果你看看Observable API
,你就会发现setChanged()
方法被保护起来了(被定义成protected
)。这意味着:除非你继承自Observable,否则你无法创建Observable实例并组合到你自己的对象中来。这个设计违法了第二个设计原则:“多用组合,少用继承”
观察者模式要点
- 观察者模式定义了对象之间一对多的关系。
- 主题(也就是可观察者)用一个共同的接口来更新观察者
- 观察者和可观察者之间用松耦合方式结合,可观察者不知道观察者的细节,只知道观察者实现了观察者接口。
- 使用此模式时,你可从被观察者处推(push)或拉(pull)数据(然而,推的方式被认为更“正确”)。
- 有多个观察者时,不可以依赖特定的通知次序
- Java有多重观察者模式的实现,包括了通用java.util.Observable。
- 要注意java.util.Observable实现上所带来的一些问题。
- 如果有必要的话,可以实现自己的Observable,这并不难。
- Swing大量使用观察者模式,许多GUI框架也是如此。
- 此模式业报应用在许多地方,例如:JavaBeans、RMI。
源码下载:https://github.com/dengqz/headfirst-designpatterns/tree/master/src/main/java/headfirst/designpatterns/observer
版权声明:本文为博主原创文章,欢迎转载,转载请注明作者;
原文超链接 :https://blog.csdn.net/java872114581/article/details/80332172
最后
以上就是朴素铃铛最近收集整理的关于设计模式之观察者模式的全部内容,更多相关设计模式之观察者模式内容请搜索靠谱客的其他文章。
发表评论 取消回复