资讯专栏INFORMATION COLUMN

一起学设计模式 - 观察者模式

cpupro / 1709人阅读

摘要:观察者模式是一种使用频率非常高的设计模式,无论是移动应用应用或者桌面应用,观察者模式几乎无处不在,它为实现对象之间的联动提供了一套完整的解决方案,凡是涉及到一对一或者一对多的对象交互场景都可以使用观察者模式。

观察者模式(Observer Pattern)属于对象行为型模式的一种,定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。

概述

观察者模式是一种使用率极高的模式,用于建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应作出反应。在观察者模式中,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。

观察者模式的别名包括发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式

案例

前言:观察者模式有两种方模型,分别是推模型拉模型

推模型: 主题对象向观察者推送主题的详细信息,不管观察者是否需要,推送的信息通常是主题对象的全部或部分数据。该模式下如果推送数据变了观察者都得改

拉模型: 主题对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到主题对象中获取,相当于是观察者从主题对象中拉数据。一般这种 模型的实现中,会把主题对象自身通过update()方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。

UML结构图

抽象主题(Subject)角色: 将观察者对象的引用保存在一个聚集(比如ArrayList对象)里,每个主题都可以有任何数量的观察者。抽象主题提供接口,可以增加和删除观察者对象。抽象主题角色又叫做抽象被观察者(Observable)角色。

具体主题(ConcreteSubject)角色: 将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者(Concrete Observable)角色。

抽象观察者(Observer)角色: 为所有的具体观察者定义一个更新接口,在得到主题的通知时更新自己。

具体观察者(ConcreteObserver)角色: 观察者的具体实现对象,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用。

推模式

1.定义目标对象,它知道观察它的观察者,并提供注册和删除观察者的接口

class Subject {
    /**
     * 用来保存注册的观察者对象
     */
    private List observers = new ArrayList<>();

    /**
     * 注册观察者对象
     *
     * @param observer 观察者对象
     */
    void attach(Observer observer) {
        observers.add(observer);
    }

    /**
     * 通知所有注册的观察者对象
     */
    void notifyObservers(String newState) {
        for (Observer observer : observers) {
            observer.update(newState);
        }
    }
}

2.具体的目标对象,负责把有关状态存入到相应的观察者对象,并在自己状态发生改变时,通知各个观察者

class ConcreteSubject extends Subject {

    private String subjectState;

    public String getSubjectState() {
        return subjectState;
    }

    public void change(String subjectState) {
        this.subjectState = subjectState;
        //状态发生改变,通知各个观察者
        this.notifyObservers(subjectState);
    }
}

3.创建观察者接口,定义一个更新的接口给那些在目标发生改变的时候被通知的对象

interface Observer {
    /**
     * 更新的接口
     *
     * @param subject 传入目标对象,好获取相应的目标对象的状态
     */
    void update(String subject);
}

4.具体观察者对象,实现更新的方法,使自身的状态和目标的状态保持一致

class ConcreteObserver implements Observer {

    @Override
    public void update(String newState) {
        //具体的更新实现
        //这里可能需要更新观察者的状态,使其与目标的状态保持一致
        System.out.println("接收到:" + newState);
    }
}

5.创建推模型客户端,用于测试

public class PushClient {
    
    public static void main(String[] args) {
        //创建主题对象
        ConcreteSubject subject = new ConcreteSubject();
        //创建观察者对象
        Observer observer = new ConcreteObserver();
        //将观察者对象登记到主题对象上
        subject.attach(observer);
        //改变主题对象的状态
        subject.change("push state");
    }
    
}

6.运行结果

接收到:push state
拉模式

1.定义目标对象,它知道观察它的观察者,并提供注册和删除观察者的接口

class Subject {
    /**
     * 用来保存注册的观察者对象
     */
    private List observers = new ArrayList<>();

    /**
     * 注册观察者对象
     *
     * @param observer 观察者对象
     */
    public void attach(Observer observer) {
        observers.add(observer);
    }

    /**
     * 通知所有注册的观察者对象
     */
    public void notifyObservers() {
        for (Observer observer : observers) {
            // 注意这句代码"
            observer.update(this);
        }
    }
}

2.具体的目标对象,负责把有关状态存入到相应的观察者对象,并在自己状态发生改变时,通知各个观察者

class ConcreteSubject extends Subject {
    /**
     * 示意,目标对象的状态
     */
    private String subjectState;

    public String getSubjectState() {
        return subjectState;
    }

    public void change(String subjectState) {
        this.subjectState = subjectState;
        //状态发生改变,通知各个观察者
        this.notifyObservers();
    }
}

3.创建观察者接口,定义一个更新的接口给那些在目标发生改变的时候被通知的对象

interface Observer {
    /**
     * 更新的接口
     *
     * @param subject 传入目标对象,好获取相应的目标对象的状态
     */
    void update(Subject subject);
}

4.具体观察者对象,实现更新的方法,使自身的状态和目标的状态保持一致

class ConcreteObserver implements Observer {
    /**
     * 示意,观者者的状态
     */
    private String observerState;

    @Override
    public void update(Subject subject) {
        //具体的更新实现
        //这里可能需要更新观察者的状态,使其与目标的状态保持一致
        observerState = ((ConcreteSubject) subject).getSubjectState();
        System.out.println("接收到:" + observerState);
    }
}

5.创建拉模型客户端,用于测试

public class PullClient {

    public static void main(String[] args) {
        //创建主题对象
        ConcreteSubject subject = new ConcreteSubject();
        //创建观察者对象
        Observer observer = new ConcreteObserver();
        //将观察者对象登记到主题对象上
        subject.attach(observer);
        //改变主题对象的状态
        subject.change("pull state");
    }

}

6.运行结果

接收到:pull state

上文说过推模型是假定主题对象知道观察者需要的数据,这种模型下如果数据发生变更会造成极大的影响;而拉模型是主题对象不知道观察者具体需要什么数据,没有办法的情况下,干脆把自身传递给观察者,让观察者自己去按需要取值。由此可见:拉模式的适用范围更广;

JDK中应用

对于观察者模式,其实Java已经为我们提供了已有的接口和类。对于订阅者(Subscribe,观察者)Java为我们提供了一个接口。

UML图

在JAVA语言的 java.util 库里面,提供了一个Observable类以及一个Observer接口,构成JAVA语言对观察者模式的支持。

Observer: 只定义了一个 update() 方法,当被观察者对象的状态发生变化时,被观察者对象的 notifyObservers() 方法就会调用这一方法。

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

Observable: 充当观察目标类,在Observable中定义了一个向量Vector来存储观察者对象。一个观察目标类可以有多个观察者对象,每个观察者对象都是实现Observer接口的对象。在被观察者发生变化时,会调用ObservablenotifyObservers()方法,此方法调用所有的具体观察者的update()方法, 从而使所有的观察者都被通知更新自己。

setChanged() 设置一个内部标记变量,代表被观察者对象的状态发生了变化。

notifyObservers()调用所有登记过的观察者对象的update()方法,使这些观察者对象可以更新自己。

public class Observable {
    private boolean changed = false; //是否改变状态,每次都需要设置,表示内容发生变化
    private Vector obs; //Vector利用同步方法来线程安全,线程安全在多线程情况下不会造成数据混乱

    /** Construct an Observable with zero Observers. */

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

    //通知方法,用于在方法内部循环调用向量中每一个观察者的update()方法。
    public void notifyObservers() {
        notifyObservers(null);
    }


    public void notifyObservers(Object arg) {

        Object[] arrLocal;

        synchronized (this) {
            if (!changed) //状态值未改变时返回,不通知
                return;
            arrLocal = obs.toArray(); //将Vector转换成数组
            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();
    }
}
小案例

1.定义两个实现了实现java.util.Observer接口的观察者

class SubscribeReader implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        System.out.println("开始读取:" + ((Publish) o).getMessage());
    }
}

class SubscribeWrite implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        System.out.println("开始写入:" + ((Publish) o).getMessage());
    }
}

2.创建继承java.util.Observable的通知者

class Publish extends Observable {
    private String message;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
        //改变通知者的状态
        super.setChanged();
        //调用父类Observable方法,通知所有观察者
        super.notifyObservers();
    }
}

3.创建测试客户端

public class Client {

    public static void main(String[] args) {
        Publish publish = new Publish();
        // 遵循FIFO 模型 先进后出
        SubscribeWrite write = new SubscribeWrite();
        SubscribeReader reader = new SubscribeReader();
        publish.addObserver(reader);
        publish.addObserver(write);
        publish.setMessage("Hello Battcn");
        publish.setMessage("QQ:1837307557");
        publish.setMessage("Email:1837307557@qq.com");
    }

}

4.运行结果

开始写入:Hello Battcn
开始读取:Hello Battcn
开始写入:QQ:1837307557
开始读取:QQ:1837307557
开始写入:Email:1837307557@qq.com
开始读取:Email:1837307557@qq.com
观察者模式与MVC

在当前流行的MVC(Model-View-Controller)架构中也应用了观察者模式,MVC是一种架构模式,它包含三个角色:模型(Model),视图(View)和控制器(Controller)。其中模型可对应于观察者模式中的观察目标,而视图对应于观察者,控制器可充当两者之间的中介者。当模型层的数据发生改变时,视图层将自动改变其显示内容。

总结

实现的关键是要建立观察者和被观察者之间的联系、比如在被观察者类中有个集合是用于存放观察者的、当被检测的东西发生改变的时候就要通知所有观察者。在被观察者中要提供一些对所有观察者管理的一些方法.目的是添加或者删除一些观察者.这样才能让被观察者及时的通知观察者关系的状态已经改变、并且调用观察者通用的方法将变化传递过去。

在实现观察者模式,如果JDK的Observable类和一个Observer接口能满足需求,直接复用即可,无需自己编写抽象观察者、抽象主题类;

但是,java.util.Observable是一个类而不是接口,你必须设计一个类继承它。如果某个类想同时具有Observable类和另一个超类的行为,由于java不支持多重继承。所以这个时候就需要自己实现一整套观察者模式。

优点

可实现表示层和数据逻辑层的分离,定义了稳定的消息更新传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层充当具体观察者角色(Model/View)

支持广播通信,观察目标会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度(Publish/Subscribe)

实现动态联动。由于观察者模式对观察者注册实行管理,那就可以在运行期间,通过动态的控制注册的观察者,来控制某个动作的联动范围,从而实现动态联动。

缺点

如果一个被观察者对象有很多直接和间接的观察者,那么将所有的观察者都通知到会花费很多时间。

如果在观察者和被观察者之间有循环依赖的话,被观察者会触发它们形成循环调用,可能导致系统崩溃。

观察者模式没有相应的机制让观察者知道被观察者对象是怎么发生变化的,而仅仅只是知道被观察者发生了变化。

观察者模式是一种使用频率非常高的设计模式,无论是移动应用、Web应用或者桌面应用,观察者模式几乎无处不在,它为实现对象之间的联动提供了一套完整的解决方案,凡是涉及到一对一或者一对多的对象交互场景都可以使用观察者模式。

说点什么

参考文献:http://www.cnblogs.com/JsonShare/p/7270546.html

全文代码:https://gitee.com/battcn/design-pattern/tree/master/Chapter17/battcn-observer

个人QQ:1837307557

battcn开源群(适合新手):391619659

微信公众号:battcn(欢迎调戏)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/70769.html

相关文章

  • 一起并发编程 - 利用察者模式监听线程状态

    摘要:在前面的文章中介绍过观察者模式及并发编程的基础知识,为了让大家更好的了解观察者模式故而特意写了这篇番外概述在多线程下我们需要知道当前执行线程的状态是什么比如运行,关闭,异常等状态的通知,而且不仅仅是更新当前页面。 在前面的文章中介绍过 观察者模式 及 并发编程的基础知识,为了让大家更好的了解观察者模式故而特意写了这篇番外.. 概述 在Java多线程下,我们需要知道当前执行线程的状态是...

    Juven 评论0 收藏0
  • 一起设计模式 - 单例模式

    摘要:懒汉非线程安全,需要用一定的风骚操作控制,装逼失败有可能导致看一周的海绵宝宝饿汉天生线程安全,的时候就已经实例化好,该操作过于风骚会造成资源浪费单例注册表初始化的时候,默认单例用的就是该方式特点私有构造方法,只能有一个实例。 单例设计模式(Singleton Pattern)是最简单且常见的设计模式之一,主要作用是提供一个全局访问且只实例化一次的对象,避免多实例对象的情况下引起逻辑性错...

    Keven 评论0 收藏0
  • 互联网常用设计模式——通往架构师的第一步

    摘要:设计模式的分类经典应用框架中常见的设计模式分为三类创建型模式对类的实例化过程的抽象。对象的结构模式是动态的。对象的行为模式则使用对象的聚合来分配行为。设计模式是个好东西,以后肯定还要进一步的学习,并且在项目中多实践,提升自己的设计能力。 什么是设计模式? Christopher Alexander 说过:每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样...

    张红新 评论0 收藏0
  • Javascript设计模式习之Observer(察者)模式

    摘要:一个对象维持一系列依赖于它观察者的对象,将有关状态的任何变更自动通知给它们。观察者模式的实现模拟拥有的一系列依赖使用扩展对象模拟目标和在观察者列表上添加删除或通知观察者 一个对象(subject)维持一系列依赖于它(观察者)的对象,将有关状态的任何变更自动通知给它们。 当一个目标需要告诉观察者发生了什么有趣的事情,它会向观察者广播一个通知 当我们不再希望某个特定的观察者获取其注册目...

    DevWiki 评论0 收藏0
  • 【Vue原理】Vue源码阅读总结大会 - 序

    摘要:扎实基础幸好自己之前花了大力气去给自己打基础,让自己现在的基础还算不错。 写文章不容易,点个赞呗兄弟专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于 Vue版本 【2.5.17】 如果你觉得排版难看,请点击 下面链接 或者 拉到 下面关注公众号也可以吧 【Vue原理】Vue源码阅读总结大会 - 序 阅读源码是需...

    Edison 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<