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

观察者模式
目录介绍
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 参考文档:源码设计模式解析

好消息

  • 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计47篇[近20万字],转载请注明出处,谢谢!
  • 链接地址:https://github.com/yangchong211/YCBlogs
  • 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!

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源码解析文章

其他说明

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容