观察者模式

定义

观察者模式定义了对象之间的一种一对多的依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

设计原则

  • 封装变化:找出程序中会变化的方面,然后将其和固定不变的方面相分离。
  • 针对接口编程,不针对实现编程:实现松耦合,利于扩展,实现有弹性的oo系统。
  • 多用组合,少用继承:继承会使实现类变得异常冗余。

气象站实现

1. 自定义观察者模式

被观察者成为主题subject,观察者称为observer,可以向subject中注册多个observer,当subject状态改变时,通知所有的observers。

(1)定义subject和observer的接口:
public interface Subject {
    //向主题中注册观察者;
    void registerObserver(Observer o);
    //移除主题中的观察者;
    void removeObserver(Observer o);
    //通知观察者;
    void notifyObservers(double temperature, double humidity, double pressure);
}
public interface Observer {
    //更新观察者的数据;
    void update(double temp, double humidity, double pressure);
    //注销当前的观察者;
    void quit();
}
public interface Board {
    //展示发布平台内容;
    void display();
}
(2)接口的实现类:
/**
 * 气象站类,负责收集气象数据:气温,湿度,气压.
 * 当数据改变时,通知各天气发布平台.
 * Created by sarahzhou on 16/6/15.
 */
public class WeatherStation implements Subject {
    private double temperature;
    private double humidity;
    private double pressure;
    private ArrayList<Observer> observers;

    //标记数据是否更新;
    private boolean changed = false;

    public WeatherStation() {
        this.observers = new ArrayList<>();
    }

    @Override
    public void registerObserver(Observer o) {
        if (!observers.contains(o)) {
            observers.add(o);
        }
    }

    @Override
    public void removeObserver(Observer o) {
        if (observers.contains(o)) {
            observers.remove(o);
        }
    }

    @Override
    public void notifyObservers(double temperature, double humidity, double pressure) {
        int count = observers.size();
        for (int i = 0; i < count; i++) {
            observers.get(i).update(temperature, humidity, pressure);
        }
    }


    //更新天气数据;触发通知更新方法notifyObservers
    public void updateData(double temp, double humidity, double pressure) {
        this.temperature = temp;
        this.humidity = humidity;
        this.pressure = pressure;
        notifyObservers(temp, humidity, pressure);
    }
}
/**
 * 实时天气发布平台
 * Created by sarahzhou on 16/6/15.
 */
public class CurrentWeatherBoard implements Observer, Board {
    private double temperature;
    private double humidity;
    private double pressure;
    private Subject subject;

    //注册当前观察者;
    public CurrentWeatherBoard(Subject subject) {
        subject.registerObserver(this);
        this.subject = subject;
    }

    @Override
    public void update(double temp, double humidity, double pressure) {
        this.temperature = temp;
        this.humidity = humidity;
        this.pressure = pressure;
        display();
    }

    @Override
    public void quit() {
        this.subject.removeObserver(this);
    }

    @Override
    public void display() {
        System.out.println("Current:气温=" + this.temperature + ",湿度=" +
                this.humidity + ",气压=" + this.pressure);
    }
}
/**
 * 天气预报发布平台
 * Created by sarahzhou on 16/6/15.
 */
public class ForecastBoard implements Observer, Board {
    private double temperature;
    private double humidity;
    private double pressure;
    private Subject subject;

    //注册观察者;
    public ForecastBoard(Subject subject) {
        subject.registerObserver(this);
        this.subject = subject;
    }

    @Override
    public void update(double temp, double humidity, double pressure) {
        this.temperature = temp;
        this.humidity = humidity;
        this.pressure = pressure;
        display();
    }

    @Override
    public void quit() {
        this.subject.removeObserver(this);
    }

    @Override
    public void display() {
        System.out.println("Forecast:气温=" + this.temperature + ",湿度=" +
                this.humidity + ",气压=" + this.pressure + "预计未来3天将有雷雨天气!");
    }
}
(3)测试结果:
    @Test
    public void test1() {
        WeatherStation station = new WeatherStation();
        Observer o1 = new CurrentWeatherBoard(station);
        Observer o2 = new ForecastBoard(station);
        station.updateData(31.5, 68.5, 99.0);
        System.out.println("======================");
        station.updateData(25.0, 77.5, 85.0);
        System.out.println("\n==========预报平台Forecast退出============");
        o2.quit();
        station.updateData(15.5, 85.0, 90.5);
    }

数据结果为:
这里写图片描述
我们可以看到,当subject的数据更新时,观察者即可收到通知并做出相应的输出。observer负责注册和注销,subject关注推送数据,没错,我们这里使用了”推送”push这个词,有时候observer并非需要全部的数据,但是呢,我们的subject不管三七二十一把所有的数据全都push给了observer,notifyObservers(double temperature, double humidity, double pressure); 我们可能已经发现了问题:首先,observer可能收到了太多不需要的数据,如果数据很多,那么我们的方法参数岂不是很长?其次,接口的耦合性太高了,气象站要增加一个‘风力’的参数,想一下我们要变动哪些地方?subject和observer的接口要变动,其对应的实现类全部都要变动,而我们的设计原则是松耦合,将变化的部分和固定的部分分离,so我们可以采用pull拉的方式来传递数据。
在notifyObservers的时候将整个subject传递给观察者,
void notifyObservers(Subject subject);
在subject的实现类中给出每个数据的getter方法供观察者调用,对于观察者,执行update的时候,调用subject的getter方法来拉取自己需要的数据,
void update(Subject subject);
这样,我要增加‘风力’这个数据,接口不需要变动,只用在WeatherStation中增加windForce字段和它的getter方法就好啦。

2. java.util提供的观察者模式API

java为我们提供的观察者模式API包含java.util.Observable和java.util.Observer,前者是subject,后者是观察者。我们来分析下它们的code。

(1)java.util.Observer接口:
public interface Observer {
    /**
     * This method is called whenever the observed object is changed. An
     * application calls an <tt>Observable</tt> object's
     * <code>notifyObservers</code> method to have all the object's
     * observers notified of the change.
     *
     * @param   o     the observable object.
     * @param   arg   an argument passed to the <code>notifyObservers</code>
     *                 method.
     */
    void update(Observable o, Object arg);

这个接口很简单,我们自己的观察者只用实现它,在它基础上扩展就OK了,这里java即支持push推的方式,也支持pull拉的方式。若要push数据,subject需要这样调用观察者的update方法:update(null,arg); ,若要pull数据,subject需要这样调用观察者的update方法:update(subject,null);

(2)java.util.Observable类:
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;
        //判断主题是否变化,若已变化,则将所有观察者obs存入临时变量arrLocal待处理,并清除changed标记。这里需要用synchronized进行线程同步,防止多线程同时作用时发生数据错误。
        synchronized (this) {
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }
        //遍历所有的观察者,调用其update方法;
        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }

    //删除注册的所有的观察者;
    public synchronized void deleteObservers() {
        obs.removeAllElements();
    }

    //若发生变化,置changed为true,需要在我们的实现类中去调用;
    protected synchronized void setChanged() {
        changed = true;
    }

    //清除changed标记;
    protected synchronized void clearChanged() {
        changed = false;
    }
    public synchronized boolean hasChanged() {
        return changed;
    }
    public synchronized int countObservers() {
        return obs.size();
    }
}
(3)使用java提供的API实现气象站:
public class WeatherStation extends Observable {
    private double temperature;
    private double humidity;
    private double pressure;

    public void setData(double temp, double humidity, double pressure) {
        this.temperature = temp;
        this.humidity = humidity;
        this.pressure = pressure;
        setChanged();
        notifyObservers();
    }

    public double getTemperature() {
        return this.temperature;
    }

    public double getHumidity() {
        return this.humidity;
    }

    public double getPressure() {
        return this.pressure;
    }
}

这里我们采用pull拉取数据的方式;

public class ForecastBoard implements Observer, Board {
    private double temperature;
    private double humidity;

    @Override
    public void update(Observable o, Object arg) {
        if (o instanceof WeatherStation) {
            WeatherStation station = (WeatherStation) o;
            this.temperature = station.getTemperature();
            this.humidity = station.getHumidity();
            display();
        }
    }

    @Override
    public void display() {
        System.out.println(this.toString() + ":气温=" +
                this.temperature + ",湿度=" + this.humidity);
    }
}

是不是很简单呢。
测试结果如下:

    @Test
    public void test2() {
        WeatherStation station = new WeatherStation();
        ForecastBoard board1 = new ForecastBoard();
        ForecastBoard board2 = new ForecastBoard();
        station.addObserver(board1);
        station.addObserver(board2);
        station.setData(34.5, 89.0, 98.5);
        System.out.println("\n============移除board2============");
        station.deleteObserver(board2);
        station.setData(25.5, 75.0, 88.5);
    }
jdk.ForecastBoard@23ab930d:气温=34.5,湿度=89.0
jdk.ForecastBoard@4534b60d:气温=34.5,湿度=89.0

============移除board2============
jdk.ForecastBoard@4534b60d:气温=25.5,湿度=75.0

总结

其实观察者模式在我们的开发中很常用。前端开发中的事件绑定事件触发,比如为一个button绑定一个click事件,我们通常使用οnclick=’js方法();’,或者使用jquery来绑定,实际上,是调用了button组件的addActionListener方法,为其注册了监听click事件的ActionListener(即为观察者),点击了button之后,触发ActionListener的actionPerformed方法,并封装了ActionEvent对象来调用public void actionPerformed(ActionEvent e); 在这里,button是主题,ActionListener是观察者,ActionEvent是通知观察者时传递的数据。
java web模型也是一个典型的观察者模型的应用。服务器启动后,ServletContextListener一直监听浏览器请求,接收到请求后,解析分发,并作后台处理。总之,观察者模式很经典,使用中要会举一反三。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值