1. 观察者模式: 定义了对象之间的一对多依赖,当一个对象改变状态是,它的所有依赖着都会受到通知并自动更新;
2. 主题和观察者定义了一对多对象。观察者依赖于此主题,只要主题一有变化,观察者就会被通知。根据通知的风格,观察者可能因此新值而更新;
3. 观察者模式的类图:
4. 松耦合:当两个对象之间松耦合,它们依然可以交互,但是不太清楚彼此的细节;
5. 观察者模式提供了一种对象设计,让主题和观察者之间松耦合;
a. 主题只知道观察者实现了某个接口,也就是Observer接口,主题不需要知道观察者的具体类是谁、做了些什么或其他任何细节;
b. 任何时候都可以增加新的观察者。因为主题唯一依赖的东西是一个实现Observer接口的对象列表,所以我们可以随时增加观察者。事实上,在运行时我们可以用新的观察者取代现有的观察者,主题不会受到任何影响。同样的,也可以在任何时候删除某些观察者。
c. 当新类型的观察者出现时,主题的代码不需要修改。假如有个新的具体类需要当观察者,我们不需要为了兼容新类型而修改主题的代码,所有要做的就是在新的类里实现此观察者接口,然后注册为观察者即可。主题不在乎别的,它只会发送通知给所有实现了观察者接口的对象。
d. 我们可以独立地复用主题或观察者。如果我们在其他地方需要使用主题或观察者,可以轻易地复用,因为二者并非紧耦合;
e. 改变主题或观察者其中一方,并不会影响另一方。因为两者是松耦合的,所以只要他们之间的接口仍被遵守,我们就可以自由地改变他们。
6. 设计原则4:为了交互对象之间的松耦合设计而努力;
a. 松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降到了最低;
7. 气象站类图:
8. 参考Java JDK中的两种Observer设计模式:一种是“推”的方式,一种是“拉”的方式;
a. 将对象变成观察者:
i. 实现观察者接口(java.util.Observer),然后调用任何Observable对象的addObserver()方法。不想再当观察者时,调用deleteObserver()方法就可以了。
b. 可观察者要如何送出通知?
i. 首先,利用扩展java.util.Observer接口产生“可观察者”类,然后,需要两个步骤:
1) 先调用setChanged()方法,标记状态已经改变;
2) 然后调用两种notifyObserver()方法中的一个:notifyObservers()或notifyObservers(Object arg)
ii. 观察者如何接受通知?
1) 观察者实现了更新的方法,但是方法的签名不太一样:update(Observable o, Object arg);
2) 第一个参数:主题本身当作一个变量,好让观察者知道是哪个主题;
3) 第二个参数:这是传入notifyObservers()的数据对象,如果没有,则为空;
iii. 如果想“推”数据给观察者,可以吧数据当作数据对象传递给notifyObservers(arg)方法。否则,观察者就必须从可观察者对象中“拉”(pull)数据。
iv. Pull方式:
v. Push方式:
1) WeatherData.java:
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;
}
}
2) CurrentConditionsDisplay.java
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);
}
public void update(Observable obs, Object arg) {
if (obs instanceof WeatherData) {
WeatherData weatherData = (WeatherData)obs;
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
display();
}
}
public void display() {
System.out.println("Current conditions: " + temperature
+ "F degrees and " + humidity + "% humidity");
}
}
9. 不要依赖与观察者被通知的次序
a. Java.util.Observable实现了notifyObserver()方法,这导致了通知观察者的次序不同于我们先前的次序;
b. 如果我们的代码依赖这样的次序,就是错的。因为一旦观察者/可观察者的实现有所改变,通知次序就会改变,很可能就会产生错误的结果。这绝对不是我们所认为的松耦合。
10. Java.util.Observable的黑暗面
a. Observable是一个类
i. Observable是一个“类”,必须设计一个类继承它。如果某类想同时具有Observable和另一个超类的行为,就会陷入两难,Java不支持多重继承,这限制了Observable的复用潜力;
ii. 因为没有Observable接口,所以无法建立自己的实现,和Java内置的Observer API搭配使用,也无法将java.util的实现换成另一套做法的实现。
b. Observable将关键的方法保护起来
i. Observable API中,setChanged()方法被保护起来了,被定义成protected,这意味着除非你继承自Observable,否则你无法创建Observable实例并组合到你自己的对象中来。这个设计违反了第二个设计原则:“多用组合,少用继承”;
c. 解决的办法:自己实现这一套观察者模式;
11. JDK中观察者模式的其他应用:Swing API:JButton;
a. JButton的超类AbstractButton,有许多增加与删除倾听者(listener)方法,这些方法可以让观察者感应到Swing组件的不同类型时间。比如,ActionListener让你“倾听”可能发生在按钮上的动作,例如按下按钮。你可以在Swing API中找到许多不同类型的倾听者;
12. Summarize the key points:
a. 观察者模式定义了对象之间一对多的关系;
b. 主题(可观察者)用一个共同的接口来更新观察者;
c. 观察者和可观察者之间用松耦合方式结合(loosecoupling),可观察者不知道观察者的细节,只知道观察者实现了观察者接口;
d. 使用此模式时,你可从被观察者出推(push)或拉(pull)数据,但推的方式被认为更“准确”;
e. 有多个观察者时,不可以依赖特定的通知次序;
f. Java有多种观察者模式的实现,包括了通用的java.util.Observable。但要注意java.util.Observable实现上锁带来的一些问题;
g. 如果有必须,应该实现自己的观察者模式;
h. Swing大量使用观察者模式,许多GUI框架也是类似;
i. 应用此模式的许多地方:JavaBeans、RMI;
13. 几个设计原则在观察者模式中的体现:
a. 设计原则1:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起
i. 在观察者模式中,会改变的是主题的状态,以及观察者的数据和类型。用这个模式,你可以改变依赖于主题的对象,却不必改变主题。
b. 设计原则2: 针对接口编程,而不是针对实现编程
i. 主题与观察者都使用接口:观察者利用主题的接口向主题注册,而主题利用观察者接口通知观察者。这样可以让两者之间运作正常,又同时具有松耦合;
c. 设计原则3:多用组合,少用继承
i. 观察者模式利用“组合”将许多观察者组合进主题中。对象之间的这种关系不是通过继承产生的,而是在运行时利用组合的方式而产生的。
14. 源代码:
CurrentConditionsDisplay.java
package headfirst.designpatterns.observer.weatherobservable;
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);
}
public void update(Observable obs, Object arg) {
if (obs instanceof WeatherData) {
WeatherData weatherData = (WeatherData)obs;
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
display();
}
}
public void display() {
System.out.println("Current conditions: " + temperature
+ "F degrees and " + humidity + "% humidity");
}
}
DisplayElement.java
package headfirst.designpatterns.observer.weatherobservable;
public interface DisplayElement {
public void display();
}
ForecastDisplay.java
package headfirst.designpatterns.observer.weatherobservable;
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);
}
public void update(Observable observable, Object arg) {
if (observable instanceof WeatherData) {
WeatherData weatherData = (WeatherData)observable;
lastPressure = currentPressure;
currentPressure = weatherData.getPressure();
display();
}
}
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");
}
}
}
HeatIndexDisplay.java
package headfirst.designpatterns.observer.weatherobservable;
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);
}
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();
}
}
public void display() {
System.out.println("Heat index is " + heatIndex);
}
}
StatisticsDisplay.java
package headfirst.designpatterns.observer.weatherobservable;
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);
}
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();
}
}
public void display() {
System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings)
+ "/" + maxTemp + "/" + minTemp);
}
}
WeatherData.java
package headfirst.designpatterns.observer.weatherobservable;
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;
}
}
WeatherStation.java
package headfirst.designpatterns.observer.weatherobservable;
public class WeatherStation {
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);
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 29.2f);
}
}
WeatherStationHeatIndex.java
package headfirst.designpatterns.observer.weatherobservable;
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);
}
}
SwingObserverExample.java
package headfirst.designpatterns.observer.swing;
import java.awt.*;
import javax.swing.*;
public class SwingObserverExample {
JFrame frame;
public static void main(String[] args) {
SwingObserverExample example = new SwingObserverExample();
example.go();
}
public void go() {
frame = new JFrame();
JButton button = new JButton("Should I do it?");
// Without lambdas
//button.addActionListener(new AngelListener());
//button.addActionListener(new DevilListener());
// With lambdas
button.addActionListener(event ->
System.out.println("Don't do it, you might regret it!")
);
button.addActionListener(event ->
System.out.println("Come on, do it!")
);
frame.getContentPane().add(BorderLayout.CENTER, button);
// Set frame properties
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(BorderLayout.CENTER, button);
frame.setSize(300,300);
frame.setVisible(true);
}
/*
* Remove these two inner classes to use lambda expressions instead.
*
class AngelListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
System.out.println("Don't do it, you might regret it!");
}
}
class DevilListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
System.out.println("Come on, do it!");
}
}
*/
}