zoukankan      html  css  js  c++  java
  • Java描述设计模式(11):观察者模式

    本文源码:GitHub·点这里 || GitEE·点这里

    一、观察者模式

    1、概念描述

    观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式。观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,主题对象在状态发生变化时,会通知所有观察者对象。Redis和常用消息中间件的发布订阅模式,都是基于该原理实现。

    2、核心角色

    • 抽象主题角色

    抽象主题角色把所有对观察者对象的统一聚集管理,每个主题都可以有一个或多个观察者。抽象主题提供一个接口,可以增加和删除观察者对象,抽象主题角色又叫做抽象被观察者(Observable)角色。

    • 具体主题角色

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

    • 抽象观察者角色

    为所有的具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。

    • 具体观察者角色

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

    3、源代码实现

    • 基于消息推送模式

    主题对象向观察者推送主题的消息,不管观察者是否需要。

    /**
     * 观察者设计模式
     */
    public class C01_Observer {
        public static void main(String[] args) {
            // 创建主题对象
            ConcreteSubject subject = new ConcreteSubject();
            // 创建观察者对象
            Observer observer1 = new ConcreteObserver("观察者A");
            Observer observer2 = new ConcreteObserver("观察者B");
            // 注册观察者
            subject.attach(observer1);
            subject.attach(observer2);
            // 修改主题状态
            subject.change("New State !");
            /**
             * 主题状态:New State !
             *【观察者A】状态:New State !
             *【观察者B】状态:New State !
             */
        }
    }
    // 抽象主题角色
    abstract class Subject {
        // 保存注册的观察者对象
        private List<Observer> list = new ArrayList<>();
        /**
         * 注册观察者对象
         */
        public void attach (Observer observer){
            list.add(observer);
            System.out.println("注册一个观察者:"+observer.getClass().getName());
        }
        /**
         * 删除观察者对象
         */
        public void delete (Observer observer){
            list.remove(observer);
            System.out.println("删除一个观察者:"+observer);
        }
        /**
         * 通知所有注册的观察者对象
         */
        public void notifyObserver (String newState){
            for (Observer observer : list) {
                observer.update(newState);
            }
        }
    }
    // 具体主题角色
    class ConcreteSubject extends Subject{
        private String state ;
        public String getState (){
            return state ;
        }
        public void change (String newState){
            state = newState;
            System.out.println("主题状态:"+state);
            //状态发生改变,通知各个观察者
            this.notifyObserver(state);
        }
    }
    // 抽象观察者角色
    interface Observer {
        /**
         * 更新接口
         */
        void update (String state);
    }
    // 具体观察者角色
    class ConcreteObserver implements Observer{
        private String name ;
        // 观察者状态
        private String observerState ;
        public ConcreteObserver (String name){
            this.name = name ;
        }
        /**
         * 更新观察者的状态,使其与目标的状态保持一致
         */
        @Override
        public void update(String state) {
            observerState = state ;
            System.out.println("【"+this.name+"】状态:"+observerState);
        }
    }
    
    • 基于消息拉取模式

    主题对象在通知观察者的时候,传递少量信息。如果观察者需要该消息内容,由观察者主动到主题对象中获取,相当于是观察者从主题对象中拉数据。

    该案例基于上述案例修改,观察者获取主题对象的消息话题,只有自己感兴趣的话题,才进一步获取内容。

    public class C02_Observer_Pull {
        public static void main(String[] args) {
            // 创建主题对象
            ConcreteSubject1 subject = new ConcreteSubject1();
            // 创建观察者对象
            Observer1 observer1 = new ConcreteObserver1("观察者A","JAVA");
            Observer1 observer2 = new ConcreteObserver1("观察者B","MySQL");
            // 注册观察者
            subject.attach(observer1);
            subject.attach(observer2);
            /*
             * 修改主题状态
             * 主题状态:JAVA State !
             * 【观察者A】状态:JAVA State !
             * 主题状态:MySQL State !
             * 【观察者B】状态:MySQL State !
             */
            subject.change("JAVA State !","JAVA");
            subject.change("MySQL State !","MySQL");
        }
    }
    abstract class Subject1 {
        // 保存注册的观察者对象
        private List<Observer1> list = new ArrayList<>();
        /**
         * 注册观察者对象
         */
        public void attach (Observer1 observer){
            list.add(observer);
        }
        /**
         * 删除观察者对象
         */
        public void delete (Observer1 observer){
            list.remove(observer);
            System.out.println("删除一个观察者:"+observer);
        }
        /**
         * 通知所有注册的观察者对象,传入消息的话题
         */
        public void notifyObservers (String msgTopic){
            for (Observer1 observer : list){
                observer.update(this);
            }
        }
    }
    class ConcreteSubject1 extends Subject1 {
        private String state ;
        private String msgTopic ;
        public String getState (){
            return state ;
        }
        public String getMsgTopic (){
            return msgTopic ;
        }
        public void change (String newState,String newMsgTopic){
            this.state = newState ;
            this.msgTopic = newMsgTopic ;
            System.out.println("主题状态:"+state);
            this.notifyObservers(msgTopic);
        }
    }
    
    interface Observer1 {
        /**
         * 更新接口
         * @param subject 传入主题对象,方面获取相应的主题对象的状态
         */
        void update(Subject1 subject);
    }
    class ConcreteObserver1 implements Observer1{
        private String name ;
        // 选择话题
        private String msgTopic ;
        // 观察者状态
        private String observerState ;
        public ConcreteObserver1 (String name,String msgTopic){
            this.name = name ;
            this.msgTopic = msgTopic ;
        }
        @Override
        public void update(Subject1 subject) {
            ConcreteSubject1 concreteSubject1 = (ConcreteSubject1)subject ;
            // 只有指定话题才拉取消息
            if (concreteSubject1.getMsgTopic().equals(msgTopic)){
                observerState = concreteSubject1.getState();
                System.out.println("【"+this.name+"】状态:"+observerState);
            }
        }
    }
    

    4、两种模式比较

    推模式是假定主题对象知道观察者需要的数据,直接推送,使得观察者对象难以复用;而拉模式是主题对象不知道观察者具体需要什么数据,将把自身传递给观察者,按需要取值。

    二、JDK中应用

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

    1、Observer接口

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

    package java.util;
    /**
     * A class can implement the <code>Observer</code> interface when it
     * wants to be informed of changes in observable objects.
    */
    public interface Observer {
        /**
         * This method is called whenever the observed object is changed. An
         * application calls an <tt>Observable</tt> object's
         */
        void update(Observable o, Object arg);
    }
    

    2、Observable类

    被观察者类都是java.util.Observable类的子类。java.util.Observable提供方法支持观察者对象。

    • setChanged方法:观察者对象的状态发生了变化。
    • notifyObservers:调用所有登记过的观察者对象的update()方法。
    package java.util;
    public class Observable {
        private boolean changed = false;
        private Vector obs;
        /** 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);
        }
        public void notifyObservers() {
    	notifyObservers(null);
        }
        /**
         * 如果本对象有变化(那时hasChanged 方法会返回true)
         * 调用本方法通知所有登记的观察者,即调用它们的update()方法
         * 传入this和arg作为参数
         */ 
        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();
        }
        /** 将“已变化”设置为true */
        protected synchronized void setChanged() {
    	changed = true;
        }
        /** 将“已变化”重置为false */
        protected synchronized void clearChanged() {
    	changed = false;
        }
        public synchronized boolean hasChanged() {
    	return changed;
        }
        public synchronized int countObservers() {
    	return obs.size();
        }
    }
    

    3、应用案例

    public class C03_Observer_JDK {
        public static void main(String[] args) {
            //创建被观察者对象
            MsgSource msgSource = new MsgSource();
            //创建观察者对象,并将被观察者对象登记
            MsgConsumer watcher = new MsgConsumer(msgSource);
            msgSource.setData("Hello,Java");
            msgSource.setData("Bye Java");
        }
    }
    class MsgSource extends Observable {
        private String data = "";
        public String getData() {
            return data;
        }
        public void setData(String data) {
            if(!this.data.equals(data)){
                this.data = data;
                setChanged();
            }
            notifyObservers();
        }
    }
    class MsgConsumer implements java.util.Observer {
        // 添加观察者
        public MsgConsumer(Observable msgSource){
            msgSource.addObserver(this);
        }
        // 状态获取
        @Override
        public void update(Observable o, Object arg) {
            System.out.println("消息内容:" + ((MsgSource)o).getData());
        }
    }
    

    三、优缺点总结

    观察者模式的主要的作用是对象解耦,将观察者和被观察者隔离。

    程序中包括多个被观察者和多个被观察者,开发和调试比较复杂,而且Java中的消息的通知默认是顺序执行的,一个观察者的执行阻塞会影响整体的执行效率。

    四、源代码地址

    GitHub·地址
    https://github.com/cicadasmile/model-arithmetic-parent
    GitEE·地址
    https://gitee.com/cicadasmile/model-arithmetic-parent
    

  • 相关阅读:
    ICPC-Beijing 2006 狼抓兔子
    【模板】多项式求逆
    AHOI2014/JSOI2014 奇怪的计算器
    Hnoi2013 切糕
    Ahoi2014&Jsoi2014 支线剧情
    bzoj3774 最优选择
    WC2019游记
    HNOI2007 分裂游戏
    bzoj1457 棋盘游戏
    poj2484 A Funny Game
  • 原文地址:https://www.cnblogs.com/cicada-smile/p/11525042.html
Copyright © 2011-2022 走看看