设计模式11-观察者模式

概念


定义:

观察者模式定义了对象间的一种一对多的依赖关系。当“一”的对象状态发生变化时,有依赖于它的所有对象都得到通知并且自动更新,其本质是触发联动

结构:

观察者模式主要有两个角色,”主题”(Subject)和”观察者”()。如果把这种一对多的关系比作报社和订报者,那么“报社”就是主题,订报者就是”观察者”,所以观察者模式也叫“发布”-“订阅”模式

这里写图片描述

关键说明:

1.事实上,因为一对多,所以一的这一方,即主题必定有存储这个关系映射的,所以主题会有一个List用于存储观察者。

2.既然是List,既然是报社,那么必然存在“订阅”与”取消订阅”,“添加”与”取消”,所以一个主题类也必然需要这两个方法。

3.观察者模式是如何做到自身变化一改变,所有依赖于它的对象都得到通知?事实上,做到以下几点就行了。

  1. 在观察者内部,必须订阅了主题
  2. 主题方,在自身状态发动变化的时候,遍历主题列表下的所有观察者,调用他们的更新方法,将数据发送过去即可。
  3. 这样就实现了主题发生变化,观察者都知道的一对多关系了

小实例


这是一个报社和订报者的实例。报社一旦出新报纸,所有的订阅者都能得到一份新报纸

接口

/**
 * 目标对象,作为被观察者
 */
public interface Subject {

    public void registerObserver(Observer o);
    public void removeObserver(Observer o);
    public void notifyObservers();
}
/**
 * 观察者,比如报纸的读者
 */
public interface Observer {
    /**
     * 被通知的方法
     * @param subject 具体的目标对象,可以获取报纸的内容
     */
    public void update(Subject subject);
}

实例

public class NewsPaper implements Subject{
    /**
     * 用来保存注册的观察者对象,也就是报纸的订阅者
     */
    private List<Observer> observers;
    private String content;

    /**
     * 报纸的读者需要先向报社订阅,先要注册
     * @param reader 报纸的读者 
     * @return 是否注册成功
     */
    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }
    /**
     * 报纸的读者可以取消订阅
     * @param reader 报纸的读者
     * @return 是否取消成功
     */
    @Override
    public void removeObserver(Observer o) {
        int index = observers.indexOf(o);
        if(index>=0){
            observers.remove(index);
        }
    }
    /**
     * 当每期报纸印刷出来后,就要迅速的主动的被送到读者的手中,
     * 相当于通知读者,让他们知道
     */
    @Override
    public void notifyObservers() {
        for(Observer o :observers){
            o.update(this);
        }
    }

    public NewsPaper() {
        observers = new ArrayList<Observer>();
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
        notifyObservers();
    }

}
public class Reader implements Observer{

    private Subject subject;

    public Reader(Subject subject) {
        this.subject = subject;
        //注册阅读者
        subject.registerObserver(this);
    }

    @Override
    public void update(Subject subject) {
        if(subject instanceof NewsPaper){
            //这是采用拉的方式
            String content = ((NewsPaper)subject).getContent();
            display(content);
        }
    }

    public void display(String content){
        System.out.println("content = " + content);
    }

}

测试

public class Client {

    public static void main(String[] args) {
        //创建一个报纸,作为被观察者
        NewsPaper subject = new NewsPaper();

        //创建阅读者,也就是观察者
        Reader reader = new Reader(subject);

        Reader reader2 = new Reader(subject);

        Reader reader3 = new Reader(subject);

        //要出报纸啦
        subject.setContent("新的新闻出来啦");
    }
}

观察者模式”推”和”拉”


观察者模式有推拉两种形式,上面展示的是拉的方式,什么叫拉,即订阅者主动向主题去拿取数据。核心代码如下:

o.update(this);

这是在subject类内的notifyObservers方法,它采取什么样的形式去通知呢?它把自己当做参数传送过去了。

String content = ((NewsPaper)subject).getContent();

这是Observer类内的update方法,它怎么得到通知的呢?它主动从参数中拉取它需要的信息。

综上所述,这是观察者拉的模式。

那么观察者推的模式是怎样的?
事实上,只是参数的问题。拉的方式,主题将自身传过去,让观察者自己从这个对象中拉取。那么推的方式,就是将具体的通知推过去,观察者就能直接使用这个通知。核心代码如下:

o.update(content);

String content = content;

当然,你需要把观察者的update方法的形参定义为String类型,不然是不可能通过的。

java中内置的观察者模式


我们看起来会发现,其实Subject接口和Observer接口事实上很简单,难道我们以后使用观察者模式都需要再做这么两个接口吗?总是这样重复造轮子?

事实上不用的,java已经实现了观察者模式的相关接口,这两个重要的接口 (java.util.Observable)和(java.util.Observer)

是的,观察者还是叫观察者,但是主题变成了可观察者了,但是大概意思还是一致的。


那么,我们如何把一个对象变成观察者呢?三个步骤

1.对象实现观察者接口 (java.util.Observer)
2.调用实现Observable接口对象的addObserver方法
3.如果不想当该Observable接口的观察这,调用deleteObserver()方法

那么如何把一个对象变成可观察者(主题)呢?
1.扩展java.util.Observable,注意,是扩展,所以是继承
2.先调用setChanged()方法,标记状态为已改变
3.调用notifyObservers()或notifyObservers(Object arg)方法中的一个


实例

/**
 * 真正的读者,为了简单就描述一下姓名
 */
public class Reader implements java.util.Observer{
    /**
     * 读者的姓名
     */
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public void update(Observable o, Object obj) {
        //同时支持 推模型 和 拉模型

        //这是采用推的方式
        System.out.println(name+"收到报纸了,阅读先。目标推过来的内容是==="+obj);
        //这是获取拉的数据
        System.out.println(name+"收到报纸了,阅读先。主动到目标对象去拉的内容是==="
        +((NewsPaper)o).getContent());
    }

}
/**
 * 报纸对象,具体的目标实现
 */
public class NewsPaper extends java.util.Observable{
    /**
     * 报纸的具体内容
     */
    private String content;
    /**
     * 获取报纸的具体内容
     * @return 报纸的具体内容
     */
    public String getContent() {
        return content;
    }
    /**
     * 示意,设置报纸的具体内容,相当于要出版报纸了
     * @param content 报纸的具体内容
     */
    public void setContent(String content) {
        this.content = content;
        //内容有了,说明又出报纸了,那就通知所有的读者
        //注意在用Java中的Observer模式的时候,这句话不可少
        this.setChanged();
        //然后主动通知,这里用的是推的方式
//      this.notifyObservers(this.content);
        //如果用拉的方式,这么调用
        this.notifyObservers();
    }
}
public class Client {
    public static void main(String[] args) {
        //创建一个报纸,作为被观察者
        NewsPaper subject = new NewsPaper();

        //创建阅读者,也就是观察者
        Reader reader1 = new Reader();
        reader1.setName("张三");

        Reader reader2 = new Reader();
        reader2.setName("李四");

        Reader reader3 = new Reader();
        reader3.setName("王五");

        //注册阅读者
        subject.addObserver(reader1);
        subject.addObserver(reader2);
        subject.addObserver(reader3);

        //要出报纸啦
        subject.setContent("本期内容是观察者模式");
        System.out.println("----------->");
    }
}

这个我觉得重点在于推拉两种模式,即notifyObservers()和notifyObservers(Object obj)两个方法,第一个方法表示采用拉的方式,第二个方法表示采用推的方式。具体我们可以看下源码

 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 void notifyObservers() {
        notifyObservers(null);
    }

notifyObservers()方法,采用拉的方式,它会将null传过去。那么在观察者出的update获取到的参数就必定为null了,但是观察者仍然可以用拉取的方式拿到数据

notifyObservers(Object arg)方法,采用推的方式,将参数送给观察者,观察者可以直接拿到数据。

关于可观察者为什么必须调用.setChanged()方法,这里也有给一个说明,因为changed必须为true,才能执行消息通知的功能。

关于通知的顺序


对比我们自己造轮子和使用java内置的轮子,你会发现,通知的顺序不一样。本来是ABC顺序,但是java内置可能不是按这样的顺序。
但是我们需要知道,我们的重点是主题发生变化时,观察者得到通知,其先后顺序不应该得到具体确定,这也不是我们业务必须要的功能,多个观察者之间的功能也应该是平行的,相互之间也不应该有类似链条一样的先后依赖关系

现实中JDK的观察者模式


现实中有一个非常著名的观察者模式,就是Swing下的监听器。我们都知道,swing可以在一个组件上加上很多监听器addXXXListener。比如,一个button按钮,我们可以给他加上左键点击,右键点击等等触发事件。
这里主题就是组件,监听器就是观察者!这就是观察者模式了,不过这是变异了的观察者模式,它需要根据主题的不同状态变化来产生不一样的通知,去通知监听器说该组件已经被触发了。
所以Swing下的观察者模式是需要根据主题的不同变化区别通知不同的观察者,那么这是如何做到的呢?这就是观察者模式的简单变形–区别对待观察者

区别对待观察者


举个例子:微信的朋友圈,有多种可见,给某人看,不给某人看,所有人都可以看。
我们就能区别三种观察者了。

那么,我们可以有两种做法:
1.目标可以通知所有人,即三种观察者我们都通知到,但是对于非这类观察者,我们不做任何操作。
比如:我们设为给某人看,但是微信直接把信息通知给了所有人,但是除了某人,其他人不做任何操作,那么其他人也不会看到这条朋友圈了

2.在目标内部进行判断,如果非该类观察者,那就不通知了,只通知这类观察者。
比如:我们设为给某人看,微信内部就判断,其他人不是某人,那么其他人不收到通知。而某人刚好就是这类观察者,那么某人就会得到通知,且做出操作

一个观察者观察多个目标的问题


事实上我们从现实的swing还可以学到一招,那就是:
如果是一个观察者观察多个目标,那该怎么办?

学习的时候,视频描述了两种,但我发现还可以有第三种,事实上因人而异,可能还会有第四种,第五种等等。

第一种:
观察者的更新方法多一个形参,不同的目标传递不一样的实参,更新方法内根据参数判断是哪个目标。

第二种:
定义不一样的回调方法,这个回调方法指的就是更新方法。比如观察者观察了A主题,B主题,C主题。那么就定义一个A主题的更新方法,B主题的更新方法,C主题的更新方法

第三种:
就是我上面用到的使用的,但是需要是拉取的方式,以超类作为参数,面向接口编程,使用instanceof进行判断是哪个目标。

猜你喜欢

转载自blog.csdn.net/qq_32020035/article/details/80878214