情景:
气象站会实时发布气象数据,要求创建布告板,并把气象站发布的数据显示出来。
布告板会有很多,随时回添加一个或删除一个,而每个布告板显示的格式也不尽相同。
观察者模式:定义了对象之间的一对多的依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
被观察对象称作主题(Subject),依赖主题的对象称作观察者(Observer)。
当两个对象之间松耦合,它们依然可以交互,但是不太清除彼此之间的细节。
观察者模式提供了一种对象设计,让主题和观察者之间松耦合。
类图:
设计原则:为了交互对象之间的松耦合设计而努力。
气象站的实现:
主题接口:
public interface Subject { public void registerObserver(Observer o); public void removeObserver(Observer o); public void notifyObservers(); }
观察者接口:
public interface Observer { public void update(float temp, float humidity, float pressure); }
主题(气象站)实现:
import java.util.ArrayList; public class WeatherData implements Subject { private ArrayList<Observer> observers; private float temperature; private float humidity; private float pressure; public WeatherData() { observers = new ArrayList<>(); } @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 (int i = 0; i < observers.size(); i++) { 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(); } }
观察者(布告板)实现:
public interface DisplayElement { public void 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 display() { System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humdity."); } @Override public void update(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; display(); } } …………………… public class ChineseDisplay implements Observer, DisplayElement { private float temperature; private float humidity; private float pressure; private Subject weatherData; public ChineseDisplay(Subject data) { this.weatherData = data; weatherData.registerObserver(this); } @Override public void display() { System.out.println("天气状况: 气温:" + temperature + "F, 湿度:" + humidity + "%, 气压:" + pressure + " Pa。"); } @Override public void update(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; display(); } }
测试类:
public class WeatherStation { public static void main(String[] args) { WeatherData weatherData = new WeatherData(); CurrentConditionsDisplay conditionsDisplay = new CurrentConditionsDisplay(weatherData); StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData); ChineseDisplay chineseDisplay = new ChineseDisplay(weatherData); weatherData.setMeasurements(80, 65, 30.4f); System.out.println(); weatherData.setMeasurements(82, 70, 29.2f); System.out.println(); weatherData.setMeasurements(78, 90, 29.2f); } }
输出:
Current conditions: 80.0F degrees and 65.0% humdity. 天气状况: 气温:80.0F, 湿度:65.0%, 气压:30.4 Pa。 Current conditions: 82.0F degrees and 70.0% humdity. 天气状况: 气温:82.0F, 湿度:70.0%, 气压:29.2 Pa。
Java中自带Observable类和Observer类,可以通过继承这两个类来实现观察者模式
主题:
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; } }
观察Observable源码setChanged方法
protected synchronized void setChanged() { changed = true; }
有时候改变需要自己定义,比如温度改变0.5℃以上才算改变等……
notifyObservers方法的实现:
public void notifyObservers(Object arg) { Object[] arrLocal; synchronized (this) { if (!changed) return; arrLocal = obs.toArray(); clearChanged(); } for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg); }
布告板的实现:
import java.util.Observable; import java.util.Observer; 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 + "% humdity."); } @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(); } } }
这里的update()方法和上面的实现不同,上面的实现是push的方法,也就是主题把数据直接送到观察者那里。而这里的是观察者通过getter()方法来自己pull所需要的数据。
但是java.util.Observable和java.util.Observer都是类而不是接口,所以要继承它们就无法继承别的类了。
JDK中的观察者模式
Swing中的JButton就用到了观察者模式。
import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; 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?"); button.addActionListener(new AngelListener()); button.addActionListener(new DevilListener()); frame.getContentPane().add(BorderLayout.CENTER, button); frame.setSize(200, 200); frame.setVisible(true); } // 一个ActionListener就是一个观察者 class AngelListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { System.out.println("Dont do it, you might regret it!"); } } class DevilListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { System.out.println("Come on, do it!"); } } }