Java设计模式之观察者模式

Java设计模式之观察者模式

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

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

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

      Java设计模式之观察者模式_第1张图片
      
      
     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 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中消息的通知默认是顺序执行,一个观察者卡壳,会影响整体的执行效率。在这种情况下,一般考虑采用异步的方式。多级触发时的效率更是让人担忧,希望在设计的时候注意考虑一下这方面的问题。

你可能感兴趣的:(Java之设计模式,java设计模式,观察者模式,构架,java)