• 设计模式(三)--观察者模式


      最近一直没有什么状态,无心学习,业余时间不能再宅在家里了,宅男都有神经病,整天对着手机说话,不出门,没朋友,我说的对吗?siri?博客不定期更新,爱看不看,反正也没几个人看。还有,男人都要像灭霸一样,即使是反派,也要光明磊落,说不害谁就不害谁,更何况是队友呢?

      记录一下观察者模式,这个模式应该是项目中最可能用到的模式之一了,话不多说,进入正题。

      什么是观察者模式,我自己理解,就是一个类管理着所有依赖于它的观察者类,并且它状态变化时会主动给这些依赖它的类发出通知。

      下面给出观察者模式的类图。

      我们根据UML图翻译成java代码,首先是观察者接口。

    /**
     * 观察者接口
     * @author xiaodongdong
     * @create 2018-05-16 17:02
     **/
    public interface Observer {
        void update(Observable o);
    }

      接下来是具体的观察者。

    /**
     * 具体观察者1
     * @author xiaodongdong
     * @create 2018-05-16 17:04
     **/
    public class ConcreteObserver1 implements Observer {
        @Override
        public void update(Observable o) {
            System.out.println("观察者1观察到" + o.getClass().getSimpleName() + "发生变化");
            System.out.println("观察者1做出相应");
        }
    }
    /**
     * 具体观察者2
     * @author xiaodongdong
     * @create 2018-05-16 17:05
     **/
    public class ConcreteObserver2 implements Observer {
        @Override
        public void update(Observable o) {
            System.out.println("观察者2观察到" + o.getClass().getSimpleName() + "发生变化");
            System.out.println("观察者2做出相应");
        }
    }

      接下来是被观察者,被观察者持有一个观察者的集合,一旦被观察者发生变化,会主动调用观察者的接口方法通知相关观察者。

    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 被观察者
     * @author xiaodongdong
     * @create 2018-05-16 17:06
     **/
    public class Observable {
        List<Observer> observers = new ArrayList<Observer>();
    
        public void addObserver(Observer o){
            observers.add(o);
        }
    
        public void changed(){
            System.out.println("我是被观察者,我已经发生变化了");
            //通知观察自己的所有观察者
            notifyObservers();
        }
    
        public void notifyObservers(){
            for (Observer observer : observers) {
                observer.update(this);
            }
        }
    }

      测试一下。

    /**
     * 测试
     * @author xiaodongdong
     * @create 2018-05-16 17:09
     **/
    public class Client {
        public static void main(String[] args) throws Exception {
            Observable observable = new Observable();
            observable.addObserver(new ConcreteObserver1());
            observable.addObserver(new ConcreteObserver2());
            //被观察者状态改变
            observable.changed();
        }
    }

      打印结果如下。

      其实观察者是被动接收通知的,是不是能作为观察者,能不能接收通知,完全是被观察者决定。

      在网上看到一个不错的例子,写在这里。我们经常看的小说网站,都有这样的功能,就是读者可以订阅作者,作者一旦有什么新书发表,就要通知已经订阅他的读者,这是一个典型的观察者模式的应用场景。

      JDK已经帮我们写好了观察者接口和被观察者类,LZ列举一下JDK的关键实现,熟悉一下JDK的源码。

      首先是观察者接口。

    /**
     * 观察者接口,每一个观察者都必须实现这个接口
     */
    public interface Observer {
        //除了依赖被观察者,还提供了一个预留参数
        void update(Observable o, Object arg);
    }

      再是被观察者类。

    package java.util.Observable;
    
    import java.util.Observer;
    import java.util.Vector;
    
    //被观察者类
    public class Observable {
        //用来表示被观察者有没有改变
        private boolean changed = false;
        //观察者列表
        private Vector<java.util.Observer> obs;
    
    
        public Observable() {
            obs = new Vector<>();
        }
    
        //添加观察者,添加时会去重
        public synchronized void addObserver(java.util.Observer o) {
            if (o == null)
                throw new NullPointerException();
            if (!obs.contains(o)) {
                obs.addElement(o);
            }
        }
    
        //删除观察者
        public synchronized void deleteObserver(java.util.Observer o) {
            obs.removeElement(o);
        }
    
        public void notifyObservers() {
            notifyObservers(null);
        }
    
        //通知所有观察者 也就是调用观察者的update方法
        public void notifyObservers(Object arg) {
            //一个临时的数组,用于并发访问被观察者时,留住观察者列表的当前状态,这种处理方式其实也算是一种设计模式,即备忘录模式。
            Object[] arrLocal;
    
            //注意这个同步块,它表示在获取观察者列表时,该对象是被锁定的
            //也就是说,在我获取到观察者列表之后,不允许其他线程改变观察者列表
            synchronized (this) {
                //如果没变化直接返回
                if (!changed)
                    return;
                //这里将当前的观察者列表放入临时数组
                arrLocal = obs.toArray();
                //将改变标识重新置回未改变
                clearChanged();
            }
            //注意这个for循环没有在同步块,此时已经释放了被观察者的锁,其他线程可以改变观察者列表
            //但是这并不影响我们当前进行的操作,因为我们已经将观察者列表复制到临时数组
            //在通知时我们只通知数组中的观察者,当前删除和添加观察者,都不会影响我们通知的对象
            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();
        }
    }

      看注释应该能理解的差不多,但是这块有不足的地方,就是通知所有观察的地方,

    for (int i = arrLocal.length-1; i>=0; i--)
        ((Observer)arrLocal[i]).update(this, arg);

      在循环遍历观察者时,JDK没有处理update可能抛出的异常,假设有一个update方法抛出异常,那么剩下的观察者就都得不到通知了。所以我觉得这块代码可以优化成这样。

    for (int i = arrLocal.length-1; i>=0; i--){
        try {
            ((Observer)arrLocal[i]).update(this, arg);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

      我们自己做的时候,需要保证读者类中的update方法不会抛异常。JDK的观察者模式源码差不多就长这样,我们用JDK的实现把上面那个读者订阅作者的例子完成。首先要搞明白这个例子里面谁是观察者,谁是被观察者,很明显,读者是观察者,作者是被观察者,除此之外,我们还需要一个管理器来帮我们管理作者的列表,我们逐一实现,首先是读者类。

    import java.util.Observable;
    import java.util.Observer;
    
    /**
     * 读者
     * @author xiaodongdong
     * @create 2018-05-17 15:55
     **/
    public class Reader implements Observer {
    
        private String name;
    
        public Reader(String name) {
            super();
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        //关注作者
        public void subscribe(String writerName){
            WriterManager.getInstance().getWriter(writerName).addObserver(this);
        }
    
        //取关作者
        public void unsubscribe(String writerName){
            WriterManager.getInstance().getWriter(writerName).deleteObserver(this);
        }
    
        //作者发表新书时调用
        @Override
        public void update(Observable o, Object obj) {
            if (o instanceof Writer) {
                Writer writer = (Writer) o;
                System.out.println(name+"得到通知====" + writer.getName() + "发布了新书《" + writer.getLastNovel() + "》");
            }
        }
    
    }

      下面是作者类。

    //作者类,要继承自被观察者类
    public class Writer extends Observable{
        
        private String name;//作者的名称
        
        private String lastNovel;//记录作者最新发布的小说
    
        public Writer(String name) {
            super();
            this.name = name;
            WriterManager.getInstance().add(this);
        }
    
        //作者发布新小说了,要通知所有关注自己的读者
        public void addNovel(String novel) {
            System.out.println(name + "发布了新书《" + novel + "》!");
            lastNovel = novel;
            setChanged();
            notifyObservers();
        }
        
        public String getLastNovel() {
            return lastNovel;
        }
    
        public String getName() {
            return name;
        }
    
    }

      作者管理器,关键作用无非是快速查找作者,组合Map实现。

    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * 作者管理器
     * @author xiaodongdong
     * @create 2018-05-17 15:56
     **/
    public class WriterManager{
    
        private Map<String, Writer> writerMap = new HashMap<String, Writer>();
    
        //添加作者
        public void add(Writer writer){
            writerMap.put(writer.getName(), writer);
        }
        //根据作者姓名获取作者 可优化 作者名字可能重复
        public Writer getWriter(String name){
            return writerMap.get(name);
        }
    
        //单例
        private WriterManager(){}
    
        public static WriterManager getInstance(){
            return WriterManagerInstance.instance;
        }
        private static class WriterManagerInstance{
            private static WriterManager instance = new WriterManager();
        }
    }

      测试一下。

    /**
     * 测试
     * @author xiaodongdong
     * @create 2018-05-17 16:05
     **/
    public class Client {
        public static void main(String[] args) {
            //假设四个读者,两个作者
            Reader r1 = new Reader("读者1");
            Reader r2 = new Reader("读者2");
            Reader r3 = new Reader("读者3");
            Reader r4 = new Reader("读者4");
            Writer w1 = new Writer("作者1");
            Writer w2 = new Writer("作者2");
            //四人关注了作者1
            r1.subscribe("作者1");
            r2.subscribe("作者1");
            r3.subscribe("作者1");
            r4.subscribe("作者1");
            //读者3和读者4还关注了作者2
            r3.subscribe("作者2");
            r4.subscribe("作者2");
    
            //作者发布新书就会通知关注的读者
            //作者1写了设计模式
            w1.addNovel("设计模式");
            //作者2写了JAVA编程思想
            w2.addNovel("JAVA编程思想");
            //读者1取关作者1
            r1.unsubscribe("作者1");
            //作者1再写书将不会通知读者1
            w1.addNovel("观察者模式");
        }
    }

      结果如下。

      观察者模式差不多就这样,后续项目中遇到实际的例子,可以补充在后面。

  • 相关阅读:
    Math 对象
    String 对象-->split() 方法
    String 对象-->substring() 方法
    String 对象-->substr() 方法
    从零开始学 Web 之 移动Web(八)Less
    从零开始学 Web 之 移动Web(七)Bootstrap
    从零开始学 Web 之 移动Web(六)响应式布局
    从零开始学 Web 之 移动Web(五)touch事件的缺陷,移动端常用插件
    从零开始学 Web 之 移动Web(四)实现JD分类页面
    从零开始学 Web 之 移动Web(三)Zepto
  • 原文地址:https://www.cnblogs.com/peterxiao/p/9035686.html
Copyright © 2020-2023  润新知