《Head First设计模式》第二章学习笔记
一、OO基础:
抽象
二、OO原则:
为交互对象之间的松耦合设计而努力
三、观察者模式
观察者模式,又叫发布--订阅模式、模型--视图模式、源--监听器模式或从属者模式。观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。主题和观察者定义了一对多的关系,观察者依赖于此主题,只要主题状态一有变化,观察者就会被通知。
根据观察者对象引用的存储地点,观察者模式的类图有微妙的区别。观察者模式的类图有下面两种:
类图一:
类图一:
这种实现在传统的模式著作和讨论中比较常见。
类图二:
类图二:
java内置的观察者模式,属于第二种结构:主题类java.util.Observable、观察者接口java.util.Observer
观察者模式,涉及到四个角色:
- 抽象主题(Subject)角色:抽象主题提供一个接口,可以增加和删除观察者对象,主题发生变化时,可以通知所有的观察者。
- 具体主题角色:为抽象主题角色的具体实现
- 抽象观察者(Observer)角色:为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。这个接口叫做更新接口,只包含一个方法,叫做更新方法。
- 具体观察者角色:具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用。
四、例子
1、head first设计模式的例子1
实现一个气象监测应用:WeatherData记录气象站的数据,布告板负责显示气象相关的数据;当WeatherData的数据发生变化时,需要及时通知布告板更新显示的内容。采用类图一的结构,具体代码片段如下:
1)抽象主题Subject
public interface Subject {
/**
* 注册观察者
* @param o
*/
public void registerObserver(Observer o);
/**
* 删除观察者
* @param o
*/
public void removeObserver(Observer o);
/**
* 通知所有的观察者
*/
public void notifyObserver();
}
2)具体主题WeatherData类
public class WeatherData implements Subject {
private List<Observer> observerList;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
observerList = new ArrayList<Observer>();
}
@Override
public void registerObserver(Observer o) {
observerList.add(o);
}
@Override
public void removeObserver(Observer o) {
int i = observerList.indexOf(o);
if (i >= 0) {
observerList.remove(i);
}
}
@Override
public void notifyObserver() {
for (Observer obs : observerList) {
// 调用update方法,将最新数据通过参数推送给观察者
obs.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();
}
}
3)抽象观察者接口Observer
public interface Observer {
public void update(float temp, float humidity, float pressure);
}
4)具体的观察者布告板类CurrentConditionDisplay
public class CurrentConditionDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private Subject weatherData;
public CurrentConditionDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void display() {
System.out.println("Current conditions:" + temperature + "F degrees and " + humidity + "% humidity");
}
@Override
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
display();
}
}
5)测试程序
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionDisplay currentDisplay = new CurrentConditionDisplay(weatherData);
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 29.2f);
}
}
2、java内置的观察者模式
上面的例子我们从无到有地完成了观察者模式。但是,java api有内置的观察者模式:java.util.Observable类和java.util.Observer接口,它采用类图二的结构。这和我们的Subject接口与Observer接口很类似。Observable类和Observer接口使用上更方便,因为许多功能都已经事先准备好了。你甚至可以使用推或拉的方式传数据。
使用Observable类通知观察者,需要两个步骤:1)先调用setChanged()方法,标记状态已经改变的事实;2)然后调用notifyObservers()或notifyObservers(Object arg),通知观察者。带参数的方法,可以将数据通过参数推送给观察者;不带参数的方法,如果观察者需要获取数据,可以通过拉的方式获取。
借助java.util.Observable和java.util.Observer来重新第一个例子,代码片段如下:
1)主题WeatherData,继承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 void setTemperature(float temperature) {
this.temperature = temperature;
}
public float getHumidity() {
return humidity;
}
public void setHumidity(float humidity) {
this.humidity = humidity;
}
public float getPressure() {
return pressure;
}
public void setPressure(float pressure) {
this.pressure = pressure;
}
}
观察者的注册、删除、通知接口,由父类Observable来完成。
2)具体的观察者,实现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 + "% humidity");
}
@Override
public void update(Observable o, Object arg) {
if (o instanceof WeatherData) {
// 通过主题的对象引用,拉取数据
WeatherData data = (WeatherData) o;
this.temperature = data.getTemperature();
this.humidity = data.getHumidity();
display();
}
}
}
3)测试程序,见第一个例子。
本例小结:
1)java.util.Observable的黑暗面:它是一个类,不是接口,并且一些方法如setChanged的作用域是protected,因此你必须设计一个类继承它。这种设计违反了“多用组合,少用继承”的设计原则。
2)对于主题,可以使用java的不同容器来管理所有的观察者(例子1使用ArrayList,例子2使用Vector),因此,在主题变化后通知所有的观察者时,通知的顺序可能不是我们预期的。
所以,我们的代码不要依赖于观察者被通知的次序。
3、JDK中观察者模式的应用实例:JavaBean的PropertyChangeListener
抽象观察者角色:java.beans.PropertyChangeListener接口。该接口包含一个方法:void propertyChange(PropertyChangeEvent evt)
主题(被观察者),使用java.beans.PropertyChangeSupport这个类来保存注册的观察者,并负责向他们提供被观察者的变化信息。
下面通过代码示例,简单介绍PropertyChangeListener和PropertyChangeSupport的使用:当产品的标题属性值发生变化时,通知相关方处理。
1)基类BaseBean
public class BaseBean implements Serializable {
private static final long serialVersionUID = -7619685640393989429L;
protected transient PropertyChangeSupport listeners = new PropertyChangeSupport(this);
public void addPropertyChangeListener(PropertyChangeListener listener) {
listeners.addPropertyChangeListener(listener);
}
protected void firePropertyChange(String prop, Object old, Object newValue) {
listeners.firePropertyChange(prop, old, newValue);
}
public void removePropertyChangeListener(PropertyChangeListener l) {
listeners.removePropertyChangeListener(l);
}
}
2)主题类Product
public class Product extends BaseBean {
private static final long serialVersionUID = 8123607049996141812L;
private Long id;
private String title;
private String detail;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
String oldValue = this.title;
this.title = title;
firePropertyChange("title", oldValue, this.title);
}
public String getDetail() {
return detail;
}
public void setDetail(String detail) {
this.detail = detail;
}
}
其中的setTitle方法进行了处理。
3)定义两个具体的观察者类
public class TitleListenerOne implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if ("title".equals(evt.getPropertyName())) {
System.out.println("product's title changed: oldValue = " + evt.getOldValue() + ", newValue="
+ evt.getNewValue());
}
}
}
public class TitleListenerTwo implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if ("title".equals(evt.getPropertyName())) {
System.out.println("do something after product's title changed");
}
}
}
4)测试类
public class Main {
public static void main(String[] args) {
Product product = new Product();
product.addPropertyChangeListener(new TitleListenerOne());
product.addPropertyChangeListener(new TitleListenerTwo());
product.setTitle("title1");
System.out.println("======================");
product.setTitle("title2");
}
}
5)运行结果如下:
product's title changed: oldValue = null, newValue=title1
do something after product's title changed
======================
product's title changed: oldValue = title1, newValue=title2
do something after product's title changed
五、小结
观察者模式的缺点:
1)如果一个主题对象有很多直接和间接的观察者的话,将所有的观察者都通知到,会花费很多时间。
2)如果主题和观察者之间有循环依赖的话,主题会触发它们之间进行循环调用,导致系统崩溃。在使用观察者模式时要特别注意这一点。
参考资料:
1、《Head First设计模式》
2、《java与模式》