设计模式之二:观察者模式

                                          《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与模式》
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值