设计模式之观察者模式

文章思路都来自于HeadFirst

观察者模式简介

定义:观察者模式(又被称为发布-订阅(Publish/Subscribe)模式,属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己

设计场景

设计一个系统 :显示目前的状况的布告板,当weatherObject 获得最新的测量数据的时候将实时更新到布告板上。此系统的三个部分气象站,weatherdata对象(追踪来自气象站的数据,并更新布告板,可能有多个布告板分别显示不同的数据),布告板显示数据。

第一个实现

public class WeatherData {
    public void measurementsChanged() {
                private  CurrentConditionsDisplay  currentConditionsDisplay;
                private  ForecastDisplay forecastDisplay;
        float temperature = getTemperature();
        float humidity = getHumidity();
        float pressure = getPressure();
        List<Float> forecastTemperatures = getForecastTemperatures();
        currentConditionsDisplay.update(temperature, humidity, pressure);
        forecastDisplay.update(forecastTemperatures);
    }
}

实现的思路非常简单,我们的“受观察对象”从气象台获取测量数据,并保存布告板具体实现的引用,当测量数据改变的时候,调用每个布告板的update方法,实时更新数据。我们来分析这段代码的问题,首先我们使用具体实现的引用来调用更新方法,违反了设计原则:针对接口编程而非具体实现编程。当每次要添加新的布告板的时候,我们的WeatherData都得添加新的引用,这是可变的部分,我们没有封装容易改变的部分,而且我们无法在运行时动态的改变布告板。每个布告板都是我们手动的添加或者删除引用。 接下来我们采用观察者模式来解决这个问题

观察者模式实现

这里写图片描述
从此图中看出,主题(被观察者)并不需要知道观察者的具体类是谁,做了些什么或者其他任何细节。任何时候都可以增加新的观察者,他唯一依赖的就是一个实现了Observer接口的对象列表。所以我们要遵守一个设计原则:为了交互对象之间的松耦合设计而努力。松耦合的设计把对象之间的互相依赖降到了最低。
这里写图片描述
下面是实现的代码:

package designPattern;
import java.util.ArrayList;

public interface Subject {
    public void registerObserver(Observer o);
    public void removeObserver(Observer o);
    //主题状态改变,此方法会被调用通知所有的观察者
    public void notifyObservers();
}
interface  Observer{
    //当气象观测值改变时候,主题会把这些状态值当做方法的参数,传送给观察者
    void update (float temp,float humidity,float pressure);
}
interface DispalyElement{
    //  当布告板需要显示的时候,调用此方法
    void display();
}
class WeatherData implements Subject{
    private ArrayList 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(o);
        }
    }
        //这边注意只要是有变化,就会通知所有的观察者,类似于全体推送消息
    @Override
    public void notifyObservers() {
        for (int i = 0; i < observers.size(); i++) {
            Observer 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();
    }
}

//目前状况布告板
class CurrentConditionsDisplay implements  Observer,DispalyElement{
    private float temperature;
    private float humidity;
    private  Subject weatherData;
    CurrentConditionsDisplay(Subject weatherData){
        this.weatherData=weatherData;
        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 degree and"+humidity+"% humidity");
    }
}

class  WeatherStation{
    public static void main(String[] args) {
        WeatherData weatherData=new WeatherData();
        CurrentConditionsDisplay currentConditionsDisplay=new CurrentConditionsDisplay(weatherData);
        weatherData.setMeasurements(80,65,30.3f);
        weatherData.setMeasurements(78,66,30.4f);
    }
}

这段代码挺容易理解,但是有个疑问,为什么总是受观察者的数据改变之后就推送给观察者,为什么不能是观察者自主的选择要看的数据呢?
所以还有一种“拉数据”的观察者模式,java内置的观察者模式,“推拉”形式的都有,我们来看下java内置的怎么实现

package designPattern;
import java.util.Observable;
import java.util.Observer;
interface DispalyElement{
    //  当布告板需要显示的时候,调用此方法
    void display();
}
//注意这里是继承
class WeatherData extends Observable {
    /*我们不在需要追踪观察者了,也不需要管理注册与删除(让超类去做)
    所以删除了相关的代码
    所以构造器也不需要为了记住观察者而建立数据结构
    * */
    private float temperature;
    private float humidity;
    private float pressure;
    WeatherData(){ }
    void measurementsChanged(){
        //调用此方法表示状态已经改变。
        setChanged();
        //这是父类的方法,没有传送数据对象,表示我们的采用的做法是“拉”
        notifyObservers();
    }

    //利用这个方法来改变观测值
     void setMeasurements(float temperature,float humidity,float pressure){
        this.temperature=temperature;
        this.humidity=humidity;
        this.pressure=pressure;
        measurementsChanged();
    }
    //观察者利用这些方法取得WeatherData对象的状态。
    public float getHumidity() {
        return humidity;
    }
    public float getPressure() {
        return pressure;
    }
    public float getTemperature() {
        return temperature;
    }
}

//目前状况布告板
class CurrentConditionsDisplay implements Observer,DispalyElement{
    private float temperature;
    private float humidity;
    //主题对象父类,面向接口编程
    Observable observable;
    CurrentConditionsDisplay(Observable observable){
        this.observable=observable;
        //登记成为观察者
        observable.addObserver(this);
    }
    @Override
    public void display() {
        System.out.println("Current conditions:"+temperature+"F degree and"+humidity+"% humidity");
    }
    @Override
    public void update(Observable o, Object arg) {
        if(o instanceof WeatherData){
            WeatherData weatherData=(WeatherData)o;
            this.temperature=weatherData.getTemperature();
            this.humidity=weatherData.getHumidity();
            display();
        }
    }
}
class  WeatherStation{
    public static void main(String[] args) {
        WeatherData weatherData=new WeatherData();
        CurrentConditionsDisplay currentConditionsDisplay=new CurrentConditionsDisplay(weatherData);
        weatherData.setMeasurements(80,65,30.3f);
        weatherData.setMeasurements(78,66,30.4f);
    }
}

理解上面代码最好对应JDK的源码,这里我附上源码供参考

package java.util;
public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs;

    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);
    }
    public synchronized void deleteObservers() {
        obs.removeAllElements();
    }

       protected synchronized void setChanged() {
        changed = true;
    }

    java.util.Observable#notifyObservers(java.lang.Object)
     */
    protected synchronized void clearChanged() {
        changed = false;
    }
    public synchronized boolean hasChanged() {
        return changed;
    }

      public synchronized int countObservers() {
        return obs.size();
    }
}

但是要注意java内置的观察者模式有太多局限,比如它是一个类,我们必须要继承,限制了Observable的复用潜力。
总结一下:
在学习观察者模式的过程中,我们明白了一条设计规则:尽量降低交互对象之间的耦合度
1)受观察者不知道观察者的细节,只知道观察者实现了观察者接口(Update方法)
2)使用此模式可从 被观察者处推或者拉数据
3)如果有必要,最好自己实现Observable
4)在观察者模式中,会改变的是受观察者的状态(数据),以及观察者的数目和类型。用这个模式可以改变依赖于受观察者的对象,而不必更改受观察者(找出程序中会变化的部分,然后将其和固定不变得分离)
5)受观察者和观察者都使用接口:观察者利用受观察者的接口向受观察者注册,而受观察者利用观察者接口通知观察者,这样可以让两者之间正确运作,并具有松耦合的特点(针对接口编程,而不针对实现编程)
6)观察者模式利用“组合”将许多观察者自合进受观察者中。对象之间的这种关系不是通过继承产生的,而是在运行期间通过组合的方式产生的。

使用场景

使用观察者模式的场景和优缺点
使用场景
关联行为场景,需要注意的是,关联行为是可拆分的,而不是“组合”关系。
事件多级触发场景。
跨系统的消息交换场景,如消息队列、事件总线的处理机制。
优点
解除耦合,让耦合的双方都依赖于抽象,从而使得各自的变换都不会影响另一边的变换。

缺点
在应用观察者模式时需要考虑一下开发效率和运行效率的问题,程序中包括一个被观察者、多个观察者,开发、调试等内容会比较复杂,而且在Java中消息的通知一般是顺序执行,那么一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般会采用异步实现。
参考博客:http://blog.csdn.net/itachi85/article/details/50773358

课后小练习:

package designPatternExe.observer;

import java.util.ArrayList;
/*
*  * 场景描述:
 * 哈票以购票为核心业务(此模式不限于该业务),但围绕购票会产生不同的其他逻辑,如:
 * 1、购票后记录文本日志
 * 2、购票后记录数据库日志
 * 3、购票后发送短信
 * 4、其他各类活动等(考虑程序以后的扩展性)
 **/

/*分析:找到受观察者和观察者
受观察者:购票行为
观察者:看到购物后的一系列动作
核心:观察者和被观察者各自有自己的接口
* */
public class obs_1 {
    public static void main(String[] args) {
        //1:创建主题(被观察者)并且注册了通知(构造函数中)
        Observable observable=new SoldOut();
        //2:创建观察者
        Observer observer=new LogTextLog(observable);
        Observer observer1=new LogDatabaseLog(observable);
        Observer observer2=new SendMessage(observable);
        //受观察者通知观察者购票行为改变
        observable.setChanged();
    }
}
//受观察者
interface Observable{
    //添加观察者
    void registerObserver(Observer o);
    //删除观察者
    void  deleteObserver(Observer o);
    //通知观察者
    void  notifyObservers();
    void setChanged();
}

//售票行为需要被观察
class  SoldOut implements Observable{
    //观察者引用
    private  ArrayList<Observer> observers;
    public SoldOut(){
        observers=new ArrayList<>();
    }
    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }
    @Override
    public void deleteObserver(Observer o) {
        int i=observers.indexOf(o);
        if(i>0){
            observers.remove(i);
        }
    }
    @Override
    public void notifyObservers() {
        //通知所有观察者
        System.out.println("购票行为产生了");
        for (int i = 0; i < observers.size(); i++) {
            Observer o=observers.get(i);
            o.update();
        }
    }

    @Override
    public void setChanged() {
        notifyObservers();
    }
}

//定义观察者
interface  Observer{
    //更新  被通知后做的事,不同的行为有不同的实现
    void update();
}
class   LogTextLog implements Observer{
    private  Observable observable;
    public  LogTextLog(Observable observable){
        this.observable=observable;
        observable.registerObserver(this);
    }
    @Override
    public void update() {
        System.out.println("购票后记录文本日志");
    }
}
class LogDatabaseLog implements  Observer{
    private  Observable observable;
    public  LogDatabaseLog(Observable observable){
        this.observable=observable;
        observable.registerObserver(this);
    }
    @Override
    public void update() {
        System.out.println("购票后记录数据库日志");
    }
}
class  SendMessage implements  Observer{
    private  Observable observable;
    public  SendMessage(Observable observable){
        this.observable=observable;
        observable.registerObserver(this);
    }
    @Override
    public void update() {
        System.out.println("购票后发送短信");
    }
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mindcarver

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值