设计模式之----观察者模式

1.观察者模式概要

观察者模式又称为发布/订阅(Publish/Subscribe)模式,因此我们可以用报纸期刊的订阅来形象的说明:

报社方负责出版报纸.

你订阅了该报社的报纸,那么只要报社发布了新报纸,就会通知你,或发到你手上.

如果你不想再读报纸,可以取消订阅,这样,报社发布了新报纸就不会再通知你.

理解其实以上的概念,就可以理解观察者模式,观察者模式中有主题(Subject)和观察者(Observer),分别对应报社和订阅用户(你).观察者模式定义了对象之间的一对多的依赖关系,这样,当"一"的一方状态发生变化时,它所依赖的"多"的一方都会收到通知并且自动更新.如图:

设计模式之----观察者模式

简单来说既当主题更新时,所有观察者都会收到通知

2、观察者模式案例代码

主题类(Subject)

//主题接口
 interface Subject {
     //添加观察者
     void registerObserver(Observer obj);
     //移除观察者
     void deleteObserver(Observer obj);
     //当主题方法改变时,这个方法被调用,通知所有的观察者
     void notifyObserver();
}

观察者类(Oserver)

interface Observer {
    //当主题状态改变时,会将一个String类型字符传入该方法的参数,每个观察者都需要实现该方法
    public void update(String info);
}

3、案例代码

假设我们是一个报社,我们需要给订阅我们的所有用户发送报纸,用户可以随时订阅和取消订阅

①编写Obverse接口

public interface Observer {

    void updata(String message);
}

②编写Subject接口

public interface Subject {

    void registerObserver(Observer o);

    void removeObserver(Observer o);

    void notifyObserver();
}

③编写News报社主题类

public class News implements Subject {

    private ArrayList observeList;
    private String date;


    public News() {
        this.observeList = new ArrayList();
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

    @Override
    public void registerObserver(Observer o) {
        observeList.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        int i = observeList.indexOf(o);
        if (i > 0){
            observeList.remove(i);
        }
    }

    @Override
    public void notifyObserver() {
        for (int i = 0; i < observeList.size(); i++) {
            Observer o = (Observer) observeList.get(i);
            o.updata("得到"+ date +"报纸");
        }
    }
}

④编写用户类

public class User1 implements Observer{

    private String name;

    public User1(String name, Subject news) {
        this.name = name;
        news.registerObserver(this);
    }

    @Override
    public void updata(String message) {
        System.out.println(name + message);
    }
}

⑤测试代码

public static void main(String[] args) {
        //初始化声明
        News news = new News();
        User1 zhao = new User1("赵先生",news);
        User1 qian = new User1("钱先生",news);
        User1 sun = new User1("孙先生",news);
        User1 li = new User1("李先生",news);

        //修改日期
        news.setDate("3月17日");
        news.notifyObserver();
        System.out.println("-------分割线------");
        news.setDate("3月18日");
        news.notifyObserver();

    }

运行结果为

设计模式之----观察者模式

这样,我就完成了观察者模式中一人更新全员都能收到的情况了

4、问题来了?如果我不想全部接受怎么办

假如我是一个报纸订阅者,我工作繁忙只有周末才有空看报纸,而且我周一至周五不想接受报社推送的报纸

有没有办法能让我自己主动去拿报纸,而不是被动的接受报纸呢?

简单来说,推只会推送报社想要推送的数据

拉可以获取用户自己想要的数据

Java内置的API帮我们解决了这一点

java.util包下面存在这两个类,Observable类和Observer接口

前者为主题类,后者为观察者接口

我们先看下观察者接口

public interface Observer {
   
    void update(Observable o, Object arg);
}

观察者接口和我们上面设计的大体相同,但是不同的是,这里多了一个参数Observable

这个参数能够让我们知道是那个主题发来的信息,毕竟用户定报纸又不是只定一家!

我们再来看一下主题类(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);
    }

 e     java.util.Observer#update(java.util.Observable, java.lang.Object)
     */
    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;
    }

    
    protected synchronized void clearChanged() {
        changed = false;
    }

 
    public synchronized boolean hasChanged() {
        return changed;
    }

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

上面的代码很容易理解,通过函数名字就可以完全知道这个函数是干嘛的

我们重点介绍一下其中的**setChanged()**这个方法

调用setCahnged方法后,会把changed这个属性变为true

这个changed又会在那被调用呢

 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);
    }

在通过更新所有观察者的更新中,这个changed被调用了

如果为false则不通知,只有为true才通知

为什么要这样做,这样有必要吗?

其实是有必要的,我们可以给通知划分一个条件,比如报纸凌晨三点印刷完毕,我们不可能离开通知所有定报纸的用户来领,毕竟他们现在都还在睡觉呢,我们可以通过这个changed来限定一个时间,比如说到了7点中才告诉用户,今天的报纸好了

好了,介绍了这么多,我们通过使用Java内置的API看看怎么实现让用户主动拉取消息的功能

首先设计Subject类

public class News extends Observable {

    private ArrayList observeList;
    private String date;
    private String ad;


    public News() {
        this.observeList = new ArrayList();
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = "得到日期为" + date + "的报纸";
        setChanged();
        notifyObservers(this.date);
    }

    public String getAd() {
        return ad;
    }

    public void setAd(String ad) {
        this.ad = "通过报纸获得了" + ad + "的消息";
    }

   
}

在主题类里面,我们设置了两个属性,一个是广告一个是今日的报纸,只要日期改变,立马告知所有订阅者,今日的报纸出来了。但是广告改变并不会通知所有人,广告只提供给需要的人群,需要他们自己去获取

接下来,我们设计两个用户类,1是接受全部报纸推送的用户User1,2是只想看报纸里面广告的User2(因为每天报纸里面的广告也会更新)

首先我们看User1

public class User1 implements Observer {

    private String name;

    public User1(String name, Observable o) {
        this.name = name;
        o.addObserver(this);
    }

    @Override
    public void update(Observable o, Object arg) {
        System.out.println(name + arg);
    }
}

updata里面的arg参数,就是Subject类想主动推送给我们的东西,User1不管报社会推送什么,都全盘接受

接下来是User2类

public class User2 implements Observer {

    private String name;

    public User2(String name, Observable o) {
        this.name = name;
        o.addObserver(this);
    }

    @Override
    public void update(Observable o, Object arg) {
        System.out.println(name + ((News) o).getAd());
    }
}

在User2类中,User2当发现推送的时候,选择不接受arg参数,选择自己变成Subject类,自己去取Subject类里面的东西

在代码中,自己去获取了广告的信息

主函数代码

public static void main(String[] args) {
    //初始化声明
    News news = new News();
    User1 zhao = new User1("赵先生",news);
    User1 qian = new User1("钱先生",news);
    User2 sun = new User2("孙先生",news);
    User2 li = new User2("李先生",news);

    //修改日期
    news.setAd("今日不打折");
    news.setDate("3月17日");
    System.out.println("-------分割线------");
    news.setAd("今天打折");
    news.setDate("3月18日");

}

运行代码

设计模式之----观察者模式

当我们改变情况时再看看

我们只修改广告

设计模式之----观察者模式

我们发现什么都没有输出,这就表明Subject类并没有推送数据

我们也无法主动获取Subject类里面的东西

5、推和拉的好处

“推”的方式是指,Subject维护一份观察者的列表,每当有更新发生,Subject会把更新消息主动推送到各个Observer去

“拉”的方式是指,各个Observer维护各自所关心的Subject列表,自行决定在合适的时间去Subject获取相应的更新数据。

推”的好处包括

1、高效。如果没有更新发生,不会有任何更新消息推送的动作,即每次消息推送都发生在确确实实的更新事件之后,都是有意义的。

2、实时。事件发生后的第一时间即可触发通知操作。

3、可以由Subject确立通知的时间,可以避开一些繁忙时间

4、可以表达出不同事件发生的先后顺序

“拉”的好处包括

1、如果观察者众多,Subject来维护订阅者的列表,可能困难,或者臃肿,把订阅关系解脱到Observer去完成。

2、Observer可以不理会它不关心的变更事件,只需要去获取自己感兴趣的事件即可。

3、Observer可以自行决定获取更新事件的时间

4、拉的形式可以让Subject更好地控制各个Observer每次查询更新的访问权限