设计模式之三:观察者模式

yangchong211 / 文 发表于2017-11-14 19:36 次阅读
  • 观察者模式
  • 目录介绍
  • 1.观察者模式介绍
  • 2.观察者使用场景
  • 3.观察者UML图解
  • 4.观察者模式简单实现
  • 4.0 举个例子
  • 4.1 观察者代码
  • 4.2 被观察者代码
  • 4.3 测试代码
  • 4.4 思考
  • 5.观察者模式Android源码分析
  • 5.1 先来看看源代码
  • 5.2 观察者从哪里来的,查看setAdapter源代码
  • 5.3 观察者在哪里创建的呢?如何运作
  • 5.4 代码分析
  • 6.观察者模式深入探索
  • 7.EventBus事件总线
  • 7.1 遇到的问题
  • 7.2 目前流行事件总线框架
  • 7.3 源码解读,单独写成博客呢
  • 7.4 使用步骤
  • 8.其他说明
  • 8.1 参考文档:源码设计模式解析

0.本人写的综合案例

  • 案例
  • 说明及截图
  • 模块:新闻,音乐,视频,图片,唐诗宋词,快递,天气,记事本,阅读器等等
  • 接口:七牛,阿里云,天行,干货集中营,极速数据,追书神器等等
  • 持续更新目录说明:http://www.jianshu.com/p/53017c3fc75d

1.观察者模式介绍

  • 1.1 最常用的地方是GUI系统、订阅——发布系统等
  • 1.2 重要作用就是解耦,使得它们之间的依赖性更小,甚至做到毫无依赖
  • 1.3 观察者模式又被称作发布/订阅模式,观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
  • 1.4 举个例子: 就拿csdn这个类似于博客的网站来说吧,如果你订阅了或者说关注了一个领域,就能收到这个领域文章的推送,如果没有关注,则不能。是相当于有一个总控制台(被观察者,持有数据源,这里的数据源是我们每个订阅了的人)通知下面的观察者。

2.观察者使用场景

  • 事件多级触发场景
  • 跨系统的消息交换场景,如消息队列,事件总线的处理机制

3.观察者UML图解

  • 3.1 关于UML类图 Image.png
  • 3.2 角色介绍

    • 抽象主题(Subject):它把所有观察者对象的引用保存到一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
    • 具体主题(ConcreteSubject):将有关状态存入具体观察者对象;在具体主题内部状态改变时,给所有登记过的观察者发出通知。
    • 抽象观察者(Observer):为所有的具体观察者定义一个接口,在得到主题通知时更新自己。
    • 具体观察者(ConcreteObserver):实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题状态协调。
  • 3.3 其他说明
    • 1.Subject 和 Observer 是一个一对多的关系,也就是说观察者只要实现 Observer 接口并把自己注册到 Subject 中就能够接收到消息事件;
    • 2.Java API有内置的观察者模式类:java.util.Observable 类和 java.util.Observer 接口,这分别对应着 Subject 和 Observer 的角色;
    • 3.使用 Java API 的观察者模式类,需要注意的是被观察者在调用 notifyObservers() 函数通知观察者之前一定要调用 setChanged() 函数,要不然观察者无法接到通知;
    • 4.使用 Java API 的缺点也很明显,由于 Observable 是一个类,java 只允许单继承的缺点就导致你如果同时想要获取另一个父类的属性时,你只能选择适配器模式或者是内部类的方式,而且由于 setChanged() 函数为 protected 属性,所以你除非继承 Observable 类,否则你根本无法使用该类的属性,这也违背了设计模式的原则:多用组合,少用继承。

4.观察者模式简单实现

  • 4.0 举个例子
    • 就拿csdn这个类似于博客的网站来说吧,如果你订阅了或者说关注了一个领域,就能收到这个领域文章的推送,如果没有关注,则不能。是相当于有一个总控制台(被观察者,持有数据源,这里的数据源是我们每个订阅了的人)通知下面的观察者。
  • 4.1 观察者代码

    • 观察者,也就是你【程序员】,订阅专题的人
      
      public class MeObserver implements Observer {

    private String yourName; public MeObserver(String yourName){ this.yourName=yourName; } @Override public void update(Observable o, Object arg) { System.out.println("你订阅的"+arg.toString()+"更新了。"); } @Override public String toString() { return "your name "+yourName; } }

  • 4.2 被观察者代码

    • 你订阅的简书android领域。被观察者:当他有更新时,所有的观察者都会接收到响应的通知
      
      public class MeUser extends Observable {

    private String name; private int age; private String sex;

    public String getName() { return name; }

    public void setName(String name) { this.name = name; setChanged(); notifyObservers(); }

    public int getAge() { return age; }

    public void setAge(int age) { this.age = age; setChanged(); notifyObservers(); }

    public String getSex() { return sex; }

    public void setSex(String sex) { this.sex = sex; //setChanged();告知数据改变,通过notifyObservers();发送信号通知观察者。 setChanged(); notifyObservers(); }

    @Override public String toString() { return "MeUser [name=" + name + ", age=" + age + ", sex=" + sex + "]"; } }

  • 4.3 测试代码
    • 相当于服务器更新通知
      MeUser user=new MeUser();
      MeObserver coder1=new MeObserver("name1");
      MeObserver coder2=new MeObserver("name2");
      MeObserver coder3=new MeObserver("name3");
      user.addObserver(coder1);
      user.addObserver(coder2);
      user.addObserver(coder3);
      user.postNewContentToCoder("contentChanged");
  • 4.4 思考:为什么观察者是实现一个observer接口,而被观察者是继承一个抽象类呢
    • 被观察者写成抽象类的原因是复用,观察者写成接口的原因是降低代码的耦合度,面向接口编程,在原则里就是依赖倒置原则,我们倒着思考,如果这里不是接口,而是一个具体的类,那么,耦合度就相当高了,如果不是观察者注册就无法添加到observable里,就要修改observable的代码

5.观察者模式Android源码分析

  • RecycleView是Android中重要控件,其中adapter刷新数据的adapter.notifyDataSetChanged()就用到了观察者模式
  • 5.1 先来看看源代码
    /*
    * Notify any registered observers that the data set has changed.
    * 通知已登记的数据集已更改的任何观察者。
    *
    * <p>This event does not specify what about the data set has changed, forcing
    * any observers to assume that all existing items and structure may no longer be valid.
    * LayoutManagers will be forced to fully rebind and relayout all visible views.</p>
    * 此事件没有指定数据集发生了什么变化,迫使任何观察者假设所有现有的项和结构可能不再有效。
    * LayoutManagers将不得不完全重新绑定和保护所有可见的视图
    */
    public final void notifyDataSetChanged() {
    mObservable.notifyChanged();
    }
    //然后调用此方法
    public void notifyChanged() {
    // 遍历所有观察者,并且调用它们的onChanged方法
    for (int i = mObservers.size() - 1; i >= 0; i--) {
         mObservers.get(i).onChanged();
    }
    }
  • 5.2 观察者从哪里来的,查看setAdapter源代码

    public void setAdapter(Adapter adapter) {
    // bail out if layout is frozen
    setLayoutFrozen(false);
    setAdapterInternal(adapter, false, true);
    requestLayout();
    }
    private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,boolean removeAndRecycleViews) {
    //如果有adapter,那么先注销对应观察者
    if (mAdapter != null) {
        mAdapter.unregisterAdapterDataObserver(mObserver);
        mAdapter.onDetachedFromRecyclerView(this);
    }
    
    if (!compatibleWithPrevious || removeAndRecycleViews) {
        removeAndRecycleViews();
    }
    //重置
    mAdapterHelper.reset();
    final Adapter oldAdapter = mAdapter;
    mAdapter = adapter;
    if (adapter != null) {//将观察者注册到adapter中
        adapter.registerAdapterDataObserver(mObserver);
        adapter.onAttachedToRecyclerView(this);
    }
    if (mLayout != null) {
        mLayout.onAdapterChanged(oldAdapter, mAdapter);
    }
    
    mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
    mState.mStructureChanged = true;
    markKnownViewsInvalid();
    }
  • **5.3 观察者在哪里创建的呢?如何运作

    //查看源代码,可以知道
    private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
    //查看onchang方法
    private class RecyclerViewDataObserver extends AdapterDataObserver {
    @Override
    public void onChanged() {
        assertNotInLayoutOrScroll(null);
        mState.mStructureChanged = true;
        setDataSetChangedAfterLayout();
        if (!mAdapterHelper.hasPendingUpdates()) {
            requestLayout();
        }
    }
    }
    void setDataSetChangedAfterLayout() {
    if (mDataSetHasChangedAfterLayout) {
        return;
    }
    mDataSetHasChangedAfterLayout = true;
    //获取adapter中数据的数量
    final int childCount = mChildHelper.getUnfilteredChildCount();
    for (int i = 0; i < childCount; i++) {
        final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
        if (holder != null && !holder.shouldIgnore()) {
    holder.addFlags(ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
        }
    }
    
    mRecycler.setAdapterPositionsAsUnknown();
    // immediately mark all views as invalid, so prefetched views can be
    // differentiated from views bound to previous data set - both in children, and cache
    markKnownViewsInvalid();
    }
  • 5.4 代码分析
  • 在Adapter里面有一个AdapterDataObservable,是被观察者,被观察者必须有三个方法,注册,销毁,通知,这里的注册就是registerAdapterDataObserver,通知就是notify相关的。
  • 在setAdapter的时候,将观察者,也就是RecyclerViewDataObserver注册到AdapterDataObservable里面来维护,观察者里面自然是更新布局。
  • 我们调用notifyDataSetChanged其实就是调用被观察者的notify相关方法

6.观察者模式深入探索

7.EventBus事件总线

  • 7.1 遇到的问题
    • 在Activity与Fragment中通信,需要有对方的引用,会导致耦合性较高。怎么办呢?
    • 想在Activity-B中回调Activity-A的某个函数,但是Activity又不能手动创建对象设置一个listener。该怎么办呢?
    • 如何在service中更新Activity或者fragment的界面呢???
  • 7.2 目前流行事件总线框架
    • EventBus 是异步执行 使用name pattern模式,效率高,但使用不方便
    • Otto 订阅函数不是异步执行 使用注解,使用方便,但效率比不上EventBus
    • AndroidEventBus 是异步执行 订阅函数支持tag,使得事件投递更准确
  • 7.3 源码解读
    • 可以直接看EventBus源码解析文章
  • 7.4 使用步骤
    • 可以直接看EventBus源码解析文章

其他说明

  • 知乎:https://www.zhihu.com/people/yang-chong-69-24/pins/posts
  • 领英:https://www.linkedin.com/in/chong-yang-049216146/
  • 简书:http://www.jianshu.com/u/b7b2c6ed9284
  • csdn:http://my.csdn.net/m0_37700275
  • 网易博客:http://yangchong211.blog.163.com/
  • 新浪博客:http://blog.sina.com.cn/786041010yc
  • github:https://github.com/yangchong211
  • 喜马拉雅听书:http://www.ximalaya.com/zhubo/71989305/
  • 脉脉:yc930211
  • 360图书馆:http://www.360doc.com/myfiles.aspx
  • 开源中国:https://my.oschina.net/zbj1618/blog
  • 泡在网上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
  • 邮箱:yangchong211@163.com
  • 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100239.headeruserinfo.3.dT4bcV
收藏 赞 (0) 踩 (0)