• 设计模式——观察者众


    前 言

      写设计模式的技术大佬很多,布衣博主深感自身技术粗浅,本来不想人从众,但对于知识的理解、应用每个技术人还是有很大的不同的——如果把某种技术抽象来看的话,那么这种技术被不同的技术人员实现出来就会产生千人千种哈姆雷特的纷繁效果。所谓 纸上得来终觉浅,绝知此事要躬行。别人理解的终究是别人的,你看懂了不一定会运用,自己如何内化吸收,学以致用才是关键。因此,基于对技(zhuang)术( bi )的热( ke )爱(wang),博主有时间,还是会讲点设计模式方面的知识。不过设计模式毕竟是思想层面的东西,怎么运用才是关键,所以博主会重点着笔于设计模式的现实运用 ,而不只是干巴巴的概念化讲述。写的不一定好,但保证一定很用心。

    模式理解

      观察者模式,如果要好理解的话,应该称其为 发布——订阅模式。联想一下以前报纸横行的时代的订阅报纸以及如今微信时代的订阅公众号等现实场景,从对象关系模型的角度来分析,这两种场景都会涉及两大主体对象消息的订阅者和消息的发布者。产生的实际效果就是,你订阅了报纸,那么报纸发行的时候自然少不了你的一份;你订阅了某公众号,那么公众号有新动态的时候也会实时的推送到你面前。可以看出,发布者订阅者之间的关系,是一种很明显的一对多关系。不过,在正式的设计模式称谓中,我们称消息发布者为主题Subject【sʌbdʒɪkt】),称消息订阅者为观察者Observer 【əb'zɜːvə】),故博主才曰观察者众。至此,从理解层面上来说,如果你不是傻子的话,对于观察者模式,你肯定已经知其然了。

    模型分析

      就像很多道理知易行难一样,虽然观察者模式比较好理解,但是要怎么将其设计成能够开发运用的对象关系模型,还是要费一番思量。这里,博主先不直接上UML类图,度娘一下就有,先按照上面的理解,来设计草稿。从模式理解可以分析出,消息的订阅者至少需要两个基本功能:订阅取消订阅,而消息发布者的功能就是发布消息,但是发布消息应该是一种广播的方式要将更新的消息发布给所有的订阅者对象,所以,其内部应该维护一个存储所有对象引用的集合。按照这样简单的理解话,草图大概就是博主如下丑画的模样:

                

       自我感觉画的很到位,真正的在设计类图的时候,按照上图去设计,是有一定问题的。从语言描述上,订阅者根据需要订阅或取消订阅,似乎订阅和取订是订阅者自己的功能,但仔细分析则不然,应该是消息的发布者【主题】暴露给消息订阅者【观察者】的功能项。因为消息的发布者内部需要维护一个装有所有订阅者的集合,实际上就是将对集合操作(添加删除订阅者)的API暴露给了订阅者,而这也是观察者模式中 主题 对象中 注册 和 撤销注册 的本质;而至于消息发布者如何将消息发布给消息的订阅者呢?不用想复杂了,就是一个简单的发布者对订阅者方法的调用而已,只不过,发布者要通知所有订阅者的话必须要遍历集合挨个的调用订阅者的消息接收方法。在设计模式中,我们是要面向接口去设计编程模型的,于是将发布者的注册、撤销注册和订阅者的消息接收等功能都抽象成了抽象方法,将发布者和订阅者也抽象成了主题接口观察者接口。到此,基本上,UML类图你就可以画了,下图为百度词条上的UML类图:

                          

      

      需要说明的是,观察者模式只是一种设计思想,你只要搞清楚主题【Subject】观察者【Observer】之间的关联关系就够了,至于具体的抽象接口如何去设计,是要根据自己的业务需求进行灵活变动的,还可添加自己业务相关的其它功能项,以上UML图可以参考,但千万不能硬套,否则就会囿于模式,裹足难行。

    官方生态

      对于观察者模式的用武之地,博主反复查询了一下,似乎在Java的GUI工具包Swing中才有比较多的应用,不过业界似乎对Java的图形化界面开发不太感冒,博主也无力讲解。但是对于观察者模式的实现,JDK中是早就有现成的一套API——分别是 java.util.Observer接口【观察者】和 java.util.Observable 类【主题】,有兴趣的可以简单了解:

    public interface Observer {
        //主题对象发生改变,该方法被调用
        void update(Observable o, Object arg);
    }
    public class Observable {
        //标记对象状态,状态为true表示对象改变,才会通知观察对象
        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);
        }
    
        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();
        }
    
        /**
         *  标记对象状态为 true 已改变
         *  注意这是 protected 方法,也就是说,你只有继承该类才能执行状态改变,否则无法应用该类提供的通知模式 
         */
        protected synchronized void setChanged() {
            changed = true;
        }
    
        /**
         * 标记对象不再改变
         */
        protected synchronized void clearChanged() {
            changed = false;
        }
    
        public synchronized boolean hasChanged() {
            return changed;
        }
    
        /**
         * @return 返回观察者数目
         */
        public synchronized int countObservers() {
            return obs.size();
        }
    }

      这里只简单的带大家分析了下源码,如有可能,你或可以学得一些设计技巧,除此之外,就没必要深究了,因为JDK自己的这套观察者模式的 API 并不推荐大家使用,似乎也没见人用过。一方面,其作为JDK1.0 就出现的元老级API是有些过时,其存放观察者的容器是同样不推荐使用的Vector,另一方面也是最重要的一点就是其主题对象被设计成了一个类而不是接口,基于Java单继承的原则,你的类认了该类为父,那扩展性就会大打折扣。探访源码博主还发现一个细节,就是Observer 和 Observable 一出生就很孤独,竟然在JDK内部都没有两者的任何扩展(继承或实现)或引用,可见其虽为亲生,但是多么的不受待见。当然,API虽然不推荐大家使用,但是仅就源码的写法或者技巧来说,还是有值得学习的方面,怎么说呢,去其糟粕,取其精华吧!

    场景运用 

      设计模式只是将待解的问题抽象化而已,真正的运用难点是还原到实际场景本身,要结合实际场景进行灵活变化,但思想不离其宗。

      这里博主举一个线程回调的例子。我们知道,Java中线程启动后的执行的 run 方法是没有返回值的,为了获取线程任务执行的结果,通常的做法就是回调(PS:当然可以用 Future 的方式来获取异步执行结果,这也是一种方式),在 run 方法执行完毕的时候执行回调方法将子线程的执行结果返回。如果有多个地方需要子线程的执行结果,就可以采用观察者模式来进行操作。先定义主题和观察者接口:

    //主题抽象
    public interface Subject {
         void registerObserver(ICallback callback);
         void removeObserver(ICallback callback);
         void notifyObserver();
    }
    
    
    //观察者抽象 根据业务需要自己定义观察者动作
    public interface ICallback {
        void update(String result);
    }

      定义线程任务:

    public class MyThread implements Runnable, Subject {
        private List<ICallback> obs;
        private String res;
        MyThread() {
            obs = new ArrayList<>();
        }
    
        @Override
        public void run() {
            //TODO 执行线程任务  模拟执行任务延时 后获取任务结果
            try {
                TimeUnit.SECONDS.sleep(1);
                res = "号外号外,又到周末了,放假啦。。。。。。嗨起来";
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 通知观察者(们)
            notifyObserver();
        }
    
        @Override
        public void registerObserver(ICallback callback) {
            obs.add(callback);
        }
    
        @Override
        public void removeObserver(ICallback callback) {
            obs.remove(callback);
        }
    
        @Override
        public void notifyObserver() {
            //通知观察者
            for (ICallback callback : obs) {
                callback.update(res);
            }
        }
    }

      观察者们:

    public class Tom implements ICallback {
        @Override
        public void update(String result) {
            System.out.println("Tom received:"+result);
        }
    }
    
    。。。。
    
    public class Rubi implements ICallback {
    
        @Override
        public void update(String result) {
            System.out.println("Rubi received:"+result);
        }
    }

      测试:

    public class Test {
        public static void main(String[] args) {
            MyThread mt = new MyThread();
            mt.registerObserver(new Tom());  // 注册 Tom
            mt.registerObserver(new Rubi()); // 注册 Rubi
            new Thread(mt).start();   // 在主线程中开启子线程任务
        }
    }

    ----------------执行结果--------------------------------------

    Tom received:号外号外,明天又放假了。。。。。。嗨起来
    Rubi received:号外号外,明天又放假了。。。。。。嗨起来

     

      OVER !

  • 相关阅读:
    Ajax
    Linux安装SmartSVN及破解
    JQuery异步提交
    动画效果
    事件
    表单选择器
    DOM操作
    JQuery基础
    PHP环境配置
    DP--钢条切割
  • 原文地址:https://www.cnblogs.com/chenbenbuyi/p/10777825.html
Copyright © 2020-2023  润新知