Java设计模式之观察者模式

来源:互联网 发布:开关电源设计仿真软件 编辑:程序博客网 时间:2024/06/07 06:06

Java设计模式之观察者模式

  1. 观察者模式介绍
      
      观察者模式是我们项目中使用率非常高的一种设计模式,它最常用的地方就是GUI系统、订阅——发不系统,因为这个模式的一个重要的作用就是解耦,将被观察者和观察者解耦,使得他们之间的依赖性更小,甚至做到毫无依赖。

  2. 观察者模式的定义
      
      定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖与它的对象都会得到通知并被自动更新
      

  3. 观察者模式的使用场景
      
      1. 关联行为场景,需要注意的是,关联行为是可以拆分的,而不是组合关系
      2. 事件多级触发场景。
      3. 跨系统的消息交换场景,如消息队列的处理机制。
      
  4. 观察者模式的UML类图

      这里写图片描述
      
      
     4.1 Subject 被观察者
      定义被观察者必须实现的职责,他必须能够动态的添加,删除观察者。它一般是抽象或者实现类,仅仅完成作为被观察者必须实现的职责:管理观察者并通知观察者。
      
     4.2 Observer 观察者
      观察者接受到消息后,即立即进行update(更新)操作,对接收到的信息进行处理
      
     4.3 ConcreteSubject 具体的被观察者
      定义被观察者自己的业务逻辑,同时定义对哪些事件进行通知
      
     4.4 ConcreteObserver 具体的观察者
      每个观察者 在接受到消息后的处理反应是不同,哥哥观察者有自己的处理逻辑。

  5. 观察者模式的简单实现

      下面就以检测天气预报为例子,来实现一个简单的观察者模式,这里我们会举几个案例来一次讲解。
      
    IWeather.java

/** * 被观察者接口,为了简单期间,只具有两个功能,下雨、下雪和出太阳 */public interface IWeather {    //下雨    void rain();    //下雪    void snow();    //出太阳    void beSunny();}

Weather .java

/** * 具体的被观察者 */public class Weather implements IWeather{    //是否下雨    private boolean isRain;    //是否下雪    private boolean isSnow;    //是否出太阳    private boolean isSunny;    @Override    public void rain() {        System.out.println("老天开始下雨了");        isRain = true;    }    @Override    public void snow() {        System.out.println("老天开始下雪了");        isSnow = true;    }    @Override    public void beSunny() {        System.out.println("老天出太阳了");        isSunny = true;    }    /**     * 下面都是getter和setter方法     * @return     */    public boolean isRain() {        return isRain;    }    public void setRain(boolean rain) {        isRain = rain;    }    public boolean isSnow() {        return isSnow;    }    public void setSnow(boolean snow) {        isSnow = snow;    }    public boolean isSunny() {        return isSunny;    }    public void setSunny(boolean sunny) {        isSunny = sunny;    }}

IObservatory .java

/** * 观察者接口,主要来观察天气的变化 */public interface IObservatory {    /**     * 观察到天气的变化,就通知人们     * @param result     */    void upDate(String result);}

ConcreteObservatory .java

/** * 具体的观察者,天气气象站,如果观察的对象(天气)有变化,就要通知人们了 */public class ConcreteObservatory implements IObservatory{    @Override    public void upDate(String result) {        System.out.println("观察到天气的变化了,发送消息通知人们");        sendToPeople(result);        System.out.println("消息通知完毕!");    }    private void sendToPeople(String result){        System.out.println("人们接受到信息了," + result);    }}

ObservatoryInstrument .java

/** * 天气气象站的观察天气的仪器,一旦有情况就通知气象站 */public class ObservatoryInstrument extends Thread {    private Weather weather;    private ConcreteObservatory observatory;    public ObservatoryInstrument(Weather weather, ConcreteObservatory observatory) {        this.weather = weather;        this.observatory = observatory;    }    /**     * 无限监听天气情况     */    @Override    public void run() {        System.out.println("监视系统已经开启");        while (true) {            try {                //没隔三秒检测一次                Thread.sleep(2000);                System.out.println("检测了一次");            } catch (InterruptedException e) {                e.printStackTrace();            }            //如果发现在下雨,就通知天气气象站            if (weather.isRain()) {                //事实更新状态为下雨                observatory.upDate("下雨了");                //重置状态重新监控                weather.setRain(false);            } else if (weather.isSnow()) {                observatory.upDate("下雪了");                weather.setSnow(false);            } else if (weather.isSunny()) {                observatory.upDate("出太阳了");                weather.setSunny(false);            }        }    }}

Test .java

/** * 场景类 */public class Test {    public static void main(String[] args) {        //定义出被观察者天气对象        Weather weather = new Weather();        //创建具体的观察者天气气象站        ConcreteObservatory observatory = new ConcreteObservatory();        //创建监控对象        ObservatoryInstrument instrument = new ObservatoryInstrument(weather,observatory);        //开始监控        instrument.start();        //看看天气的变化情况        try {            Thread.sleep(2000);//模拟一段时间后            weather.rain();//下雨了            Thread.sleep(5000); //模拟一段时间后天气发生变化            weather.beSunny(); //出太阳了            Thread.sleep(5000);//一段时间后            weather.rain();//又下雨了        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

打印结果如下:

监视系统已经开启检测了一次老天开始下雨了检测了一次观察到天气的变化了,发送消息通知人们人们接受到信息了,下雨了消息通知完毕!检测了一次老天出太阳了检测了一次观察到天气的变化了,发送消息通知人们人们接受到信息了,出太阳了消息通知完毕!检测了一次老天开始下雨了检测了一次观察到天气的变化了,发送消息通知人们人们接受到信息了,下雨了消息通知完毕!检测了一次检测了一次检测了一次............这里无限打印检测一次

结果出来了,当天气下雨的时候,天气仪器已经监听到了气候变化,然后通过气象台通知了人们,当天气出太阳的时候也将结果告知了人们,但是这样就是我们的观察者模式了么?你会发现,虽然我们的结果是对的,但是你的CPU在一直不断的飙升,处于一直运行状态,因为在上面的这个程序中,我们使用了一个死循环来监听天气情况,要是用在项目当中,那可就惨了,不说别的,你的硬件设备就浪费了,差不多一台服务器就跑你这个死循环了。所以说,这个代码我们必须得改,这种代码是不可能放到项目中去的,并且这个完全不是面向对象的,而是面向过程的。那么我们该怎么改呢?可以这么想,既然天气一变化,仪器就能测到,并且能通知到气象站,那么我们完全可以把气象站聚集到天气这个类中呢?下面我们来开始看看修改后的代码。

Weather .java

/** * 具体的被观察者 */public class Weather implements IWeather {    private ConcreteObservatory observatory = new ConcreteObservatory();    @Override    public void rain() {        System.out.println("老天开始下雨了");        observatory.upDate("下雨了");    }    @Override    public void snow() {        System.out.println("老天开始下雪了");        observatory.upDate("下雪了");    }    @Override    public void beSunny() {        System.out.println("老天出太阳了");        observatory.upDate("出太阳了");    }}

我们将具体的被观察者修改成如上代码,然后将仪器对象移除,为了程序的简化,这里就不采用仪器来侧天气了,加入天气一有变化,气象站就能检测到天气情况,然后直接更新天气状态,测试代码修改如下。
Test.java

/** * 场景类 */public class Test {    public static void main(String[] args) {        //创建天气对象        Weather weather = new Weather();        //天气下雨        weather.rain();        try {            Thread.sleep(5000);//模拟过一段时间后,开始下雪了。            //天气下雪            weather.snow();        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

输出结果如下:

老天开始下雨了观察到天气的变化了,发送消息通知人们人们接受到信息了,下雨了消息通知完毕!老天开始下雪了观察到天气的变化了,发送消息通知人们人们接受到信息了,下雪了消息通知完毕!

这样一来,我们的气象站类就不用定义在场景类中了,运行的结果也差不多。天气发生的变化也通知到位了。不过这样就符合观察者模式了么?其实不是的,在上面的代码中,我们知道,只要天气一有变化,即气象站就会将消息发送给所有的人,对于所有人这个概念我们也是没有具体话,所以下面我们把人也稍微具体一下。观察天气的应该不只一个气象台吧,应该有几个气象台,所以,我们将多添加几个气象台,也是三个不同省的气象台监测天气情况,然后分别将天气消息发送给对应的该省的所有人,下面我们先看看修改的代码,然后做具体的讲解。

Obserable .java

/** * 被观察者接口 */public interface Obserable {    //添加一个观察者    void attachObserver(Observer observer);    //删除一个观察者    void deAttachObserver(Observer observer);    //发生状态的改变,就通知观察者    void notifyAllObservers(String result);}

WeatherObserable .java

/** * 具体的被观察者对象,天气 */public class WeatherObserable implements Obserable {    private List<Observer> observers;    public WeatherObserable(){        observers = new ArrayList<>();    }    @Override    public void attachObserver(Observer observer) {        observers.add(observer);    }    @Override    public void deAttachObserver(Observer observer) {        observers.remove(observer);    }    @Override    public void notifyAllObservers(String result) {        for (Observer observer : observers) {            observer.update(result);        }    }    public void rain(){        System.out.println("老天要下雨了");        notifyAllObservers("下雨了");    }    public void snow(){        System.out.println("老天要下雪了");        notifyAllObservers("下雪了");    }    public void beSunny(){        System.out.println("老天出太阳了");        notifyAllObservers("出太阳了");    }}

Observer.java

/** * 观察者接口 */public interface Observer {    //发现天气有变化,就通知所有观察者    void update(String result);}

HNObsertory.java

/** * 具体的观察者对象,湖南省的气象台 */public class HNObsertory implements Observer {    @Override    public void update(String result) {        System.out.println("湖南省的气象台监听到了天气变化");        sentToShanDongAllPeople(result);        System.out.println("消息发送完毕");    }    private void sentToShanDongAllPeople(String result){        System.out.println("湖南省的所有人已经收到消息了:" + result);    }}

SDObsertory.java

/** * 具体的观察者对象,山东省的气象台 */public class SDObsertory implements Observer {    @Override    public void update(String result) {        System.out.println("山东省的气象台监听到了天气变化");        sentToShanDongAllPeople(result);        System.out.println("消息发送完毕");    }    private void sentToShanDongAllPeople(String result){        System.out.println("山东省的所有人已经收到消息了:" + result);    }}

ZJObsertory.java

/** * 具体的观察者对象,浙江省的气象台 */public class ZJObsertory implements Observer {    @Override    public void update(String result) {        System.out.println("浙江省的气象台监听到了天气变化");        sentToShanDongAllPeople(result);        System.out.println("消息发送完毕");    }    private void sentToShanDongAllPeople(String result){        System.out.println("浙江省的所有人已经收到消息了:" + result);    }}

Test.java

/** * 场景类 */public class Test {    public static void main(String[] args) throws InterruptedException {        //三个气象台对象        Observer sdObserver = new SDObsertory();        Observer hnObserver = new HNObsertory();        Observer zjObserver = new ZJObsertory();        //被观察者天气对象        WeatherObserable obserable = new WeatherObserable();        //将需要监听天气的气象台观察者与被观察者天气相关联        obserable.attachObserver(sdObserver);        obserable.attachObserver(hnObserver);        obserable.attachObserver(zjObserver);        //下雪了        obserable.snow();        //模拟一段时间后的天气变化        System.out.println("*********一段时间过后***********");        Thread.sleep(5000);        //出太阳了        obserable.beSunny();    }}

输出结果如下:

老天要下雪了山东省的气象台监听到了天气变化山东省的所有人已经收到消息了:要下雪了消息发送完毕湖南省的气象台监听到了天气变化湖南省的所有人已经收到消息了:要下雪了消息发送完毕浙江省的气象台监听到了天气变化浙江省的所有人已经收到消息了:要下雪了消息发送完毕*********一段时间过后***********老天出太阳了山东省的气象台监听到了天气变化山东省的所有人已经收到消息了:要出太阳了消息发送完毕湖南省的气象台监听到了天气变化湖南省的所有人已经收到消息了:要出太阳了消息发送完毕浙江省的气象台监听到了天气变化浙江省的所有人已经收到消息了:要出太阳了消息发送完毕

  
 好了,上面的代码不仅结果是正确的,而且也符合我们的开闭原则,同时也实现了类间的解耦,如果还有哪个省份也要监听天气,直接与被观察者关联即可,只需要实现一下Observer观察者对象,然后在场景类中创建该实例,然后绑定关系。这就是我们的观察者模式。
 
6. 观察者模式的扩展实现
下面我们来对普通的观察者模式案来进行一下优化,从上面的代码,我们应该可以在其基础上进行一下抽取,从上面的WeatherObserable这个实现类中,我们可以抽象出一个父类,父类完全作为被观察者的职责,每一个被观察者只实现自己的逻辑方法就可以了,如此则非常符合单一原则了。刚好在Java中,就为我们提供了一个这样的父类,及java.util.Observable,有兴趣的大家可以自己去看一下源码,其实和我们的实现是差不多的,只是他里面多了一下安全机制,也就是使用了一个同步的方法,下面看看我们优化后的代码

IWeather.java

/** * 被观察者接口,一个已经定义好的接口,所有被观察者统一实现该接口,如果以后需要监测其他天气可以在接口 * 里面直接添加,扩展性非常好 */public interface IWeather {    //下雨    void rain();    //下雪    void snow();    //出太阳    void beSunny();}

WeatherObservable.java

/** * 优化后的具体的被观察者,天气,只需要继承java为我们提供的Observable,然后定义好的被观察者接口即可 */public class WeatherObservable extends Observable implements IWeather {    @Override    public void rain() {        System.out.println("老天要下雨了");        //记住,这条语句一定要设置,不然,下面的notifyObserver方法调用就不会有任何作用了        super.setChanged();        super.notifyObservers("要下雨了");    }    @Override    public void snow() {        System.out.println("老天要下雪了");        super.setChanged();        super.notifyObservers("要下雪了");    }    @Override    public void beSunny() {        System.out.println("老天要出太阳了");        super.setChanged();        super.notifyObservers("要出太阳了");    }}

ZJObsertory.java

/** * 优化后具体的观察者对象,浙江省的气象台,直接实现java为我们提供的Observer接口即可 */public class ZJObsertory implements Observer {    /**     * 实现系统的update方法     * @param o 被观察对象,有时候我们需要获取被观察者的一些数据     * @param arg 天气结果     */    @Override    public void update(Observable o, Object arg) {        System.out.println("浙江省的气象台监听到了天气变化");        sentToShanDongAllPeople((String)arg);        System.out.println("消息发送完毕");    }    private void sentToShanDongAllPeople(String result){        System.out.println("浙江省的所有人已经收到消息了:" + result);    }}

HNObsertory.java

/** * 具体的观察者对象,湖南省的气象台 */public class HNObsertory implements Observer {    /**     * 实现系统的update方法     * @param o 被观察对象,有时候我们需要获取被观察者的一些数据     * @param arg 天气结果     */    @Override    public void update(Observable o, Object arg) {        System.out.println("湖南省的气象台监听到了天气变化");        sentToShanDongAllPeople((String)arg);        System.out.println("消息发送完毕");    }    private void sentToShanDongAllPeople(String result){        System.out.println("湖南省的所有人已经收到消息了:" + result);    }}

SDObsertory.java

/** * 具体的观察者对象,山东省的气象台 */public class SDObsertory implements Observer {    /**     * 实现系统的update方法     * @param o 被观察对象,有时候我们需要获取被观察者的一些数据     * @param arg 天气结果     */    @Override    public void update(Observable o, Object arg) {        System.out.println("山东省的气象台监听到了天气变化");        sentToShanDongAllPeople((String)arg);        System.out.println("消息发送完毕");    }    private void sentToShanDongAllPeople(String result){        System.out.println("山东省的所有人已经收到消息了:" + result);    }}

Test.java

/** * 场景类 */public class Test {    public static void main(String[] args) throws InterruptedException {        //三个气象台对象        Observer sdObserver = new SDObsertory();        Observer hnObserver = new HNObsertory();        Observer zjObserver = new ZJObsertory();        //被观察者天气对象        WeatherObservable obserable = new WeatherObservable();        //将需要监听天气的气象台观察者与被观察者天气相关联        obserable.addObserver(sdObserver);        obserable.addObserver(hnObserver);        obserable.addObserver(zjObserver);        //下雪了        obserable.snow();        //模拟一段时间后的天气变化        System.out.println("*********一段时间过后***********");        Thread.sleep(5000);        //出太阳了        obserable.beSunny();    }}

输出结果如下:

老天要下雪了浙江省的气象台监听到了天气变化浙江省的所有人已经收到消息了:要下雪了消息发送完毕湖南省的气象台监听到了天气变化湖南省的所有人已经收到消息了:要下雪了消息发送完毕山东省的气象台监听到了天气变化山东省的所有人已经收到消息了:要下雪了消息发送完毕*********一段时间过后***********老天要出太阳了浙江省的气象台监听到了天气变化浙江省的所有人已经收到消息了:要出太阳了消息发送完毕湖南省的气象台监听到了天气变化湖南省的所有人已经收到消息了:要出太阳了消息发送完毕山东省的气象台监听到了天气变化山东省的所有人已经收到消息了:要出太阳了消息发送完毕

 
 好了,优化的观察者模式也实现了,观察者模式大概就是这样,关于java为我们提供的Observable和Observer,一定要自己去实现下,然后看看Observable类中的源码,其中还有一个方法就是notifyObservers()不带参数的通知方法,大家可以看看这个具体有什么区别,有些小细节的问题,得自己去发现,才会有收获。
  
7. 观察者模式的优点
  7.1 观察者和被观察者之间是抽象耦合
    这样的设计,则不管是增加观察者还是被观察者都非常容易扩展,而且Java中也都实现了抽象层级的定义,在系统扩展方面也是得心应手。
  7.2 建立一套触发机制
    根据单一原则,每个类的职责是单一的。
    
8. 观察者模式的缺点
  
  观察者模式需要考虑一下开发效率和运行效率问题,一个被观察者,多个观察者,开发调试就会比较复杂,而且在Java中消息的通知默认是顺序执行,一个观察者卡壳,会影响整体的执行效率。在这种情况下,一般考虑采用异步的方式。多级触发时的效率更是让人担忧,希望在设计的时候注意考虑一下这方面的问题。

1 0
原创粉丝点击