概念什么的都是正确的废话!
所以不说废话,直接上栗子:
公司中标了一个项目,要为Weather-O-Rama气象站建立下一代气象观测站!
要求:
负责跟踪目前的温度、湿度、气压的对象WeatherData
由Weather-O-Rama提供:
public class WeatherData {
public void measurementsChanged() {
// 当有新数据时,该方法被调用
}
// 温度
public float getTemperature() {
}
// 湿度
public float getHumidity() {
}
// 气压
public float getPressure() {
}
}
如何使用呢?先看个错误示范:
public void measurementsChanged() {
float temp = getTemperature();
float humidity = getHumidity();
float pressure = getPressure();
currentConditionsDisplay.update(temp, humidity, pressure);
statisticsDisplay.update(temp, humidity, pressure);
forcastsDisplay.update(temp, humidity, pressure);
}
回想策略模式里提到的3个设计原则,上面measurementsChanged()
的实现有什么问题?
我们知道,用户订阅报纸的流程是这样的:
为了更好地理解观察者模式,我们把出版社称为“主题”(Subject),把订阅者称为“观察者”(Observer)。
从订阅报纸过渡,我们可以初步设计出这样的类结构:
从上面的设计可以看到,虽然Subject
和Observer
是两个不同的对象,却依然可以交互,且无需清楚彼此的细节。
从Subject
的角度看:其唯一依赖的是一个实现Observer
接口的对象列表,所以可以随时增加或移除Observer
。
从Observer
的角度看:出现新类型的Observer
时,只需实现Observer
接口并向Subject
注册即可,Subject
不必做修改。
如此一来,改变Subject
或Observer
其中一方,都不会影响到另一方。因为两者是 松耦合 的,只要它们之间的接口仍被遵守,我们就可以自由地改变它们。
此处引出第4个设计原则:
为了交互对象之间的松耦合设计而努力。
前3个设计原则请见策略模式。
从上面的“从订阅报纸开始”得到启发后,我们尝试修改一开始糟糕的设计,重新设计气象站,类图如下:
首先创建Observer
和Subject
接口。另,因布告板的显示动作是不同于观察者的更新动作的,应该设计一个接口用于做显示气象信息的动作,于是再加一个Displayment
接口:
public interface Observer {
void update(float temp, float humidity, float pressure);
}
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObserver();
}
public interface DisplayElement {
void display();
}
然后,WeatherData
实现Subject
接口:
public class WeatherData implements Subject{
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
observers = new ArrayList<Observer>();
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
int i = observers.indexOf(observer);
if (i > 0) {
observers.remove(i);
}
}
@Override
public void notifyObserver() {
for (Observer observer : observers) {
observer.update(temperature, humidity, pressure);
}
}
public void measurementsChanged() {
notifyObserver();
}
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;
}
}
以CurrentConditionsDisplay
布告板为例,创建一个观察者:
// 该布告板显示气象站当前的观测值
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private Subject weatherData;
public CurrentConditionsDisplay(Subject weatherData) {
this.weatherData = weatherData;
this.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 degress and " + humidity + "% humidity");
}
}
启动气象站,测试程序:
(PS:StatisticsDisplay
和ForecastDisplay
是另外两个布告板,具体实现可以自行定制~)
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
Observer currentDisplay = new CurrentConditionsDisplay(weatherData);
Observer statisticsDisplay = new StatisticsDisplay(weatherData);
Observer forecastDisplay = new ForecastDisplay(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);
System.out.println();
}
}
输出结果:
Current conditions: 80.0F degress and 65.0% humidity
Avg/Max/Min temperature = 80.0/80.0/80.0
Forecast: Improving weather on the way!
Current conditions: 82.0F degress and 70.0% humidity
Avg/Max/Min temperature = 81.0/82.0/80.0
Forecast: Watch out for cooler, rainy weather
Current conditions: 78.0F degress and 90.0% humidity
Avg/Max/Min temperature = 80.0/82.0/78.0
Forecast: More of the same
至此,大家应该知道如何使用观察者模式了吧!
由于观察者种类很多,主题不能预料到每个观察者的需求,如果每次主题都有更新就通知观察者就太频繁了,比如气象站的数据一有微小的改变就会更新。。。还有一点是观察者可能不需要知道关于主题的所有状态,它们只要获取到自己需要的数据就行了。
这种情况可以改进吗?以前是主题主动push(推送)消息给观察者,可不可以主题在控制好通知频率的情况下,让观察者收到通知后从主题pull(拉取)它们所需的数据呢?
幸运的时,JDK1.0就已内置了观察者模式——提供java.util.Observable
和java.util.Observer
,下面是Observable
的关键代码:
public class Observable {
private boolean changed = false;
private Vector obs;
/** Construct an Observable with zero Observers. */
public Observable() {
obs = new Vector();
}
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
public void notifyObservers() {
notifyObservers(null);
}
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);
}
protected synchronized void setChanged() {
changed = true;
}
}
从源码可以看到,Observable
通知观察者时调用notifyObservers()
,会先判断changed
是否为真,为真才通知所有观察者。所以Observable
的子类确定要通知观察者时,要先调用父类Observable
的setChanged()
,这里有个好处就是Observable
的子类可以控制何时通知观察者,而不用一旦有微小的改变就立刻进行通知。
但是!!!java.util.Observable
是存在一些缺陷的:
1、Observable是一个类而不是接口,更糟的是它没有实现一个接口,使用者必须设计一个类继承它。如果某类想同时具有Observable
类和另一个超类的行为,就会陷入两难——因为java不支持多继承。这就限制了Observable
的复用能力。
2、setChanged()是protected的——除非继承Observable
,否则使用者无法创建Observable
实例组合到自己的对象中去。这违反了第2个设计原则:“多用组合,少用继承”。
当然,如果java.util.Observable
的缺陷不会影响你的使用,那直接利用它最方便不过了。不过其实自己实现一套观察者模式也不难~
玩过Swing的同学应该知道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?");
button.addActionListener(new AngelListener());
button.addActionListener(new DevilListener());
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);
}
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!");
}
}
}
吃完栗子可以讲正确的废话了!
观察者模式的定义:
定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。