源码地址:https://github.com/vergilyn/design-patterns

另外一个大神很全的Github:https://github.com/iluwatar/java-design-patterns

1. 概述

  观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当这个主题对象在状态发生变化时,可以通知所有观察者对象,使它们(观察者对象)能够自动更新自己。

  比如,微信用户就是观察者,微信公众号就是被观察者(主题对象),当公众号更新时会通知所有的观察者。

  以上示例更形象的表述可能是:发布/订阅(publish/subscribe)。

  (个人觉得)观察者模式的核心:其实就是触发及响应。所以感觉跟"责任链模式"有一定程度的类似,可能"责任链模式"的适用场景更加具体,而"观察者模式"相对就抽象很多。

  

2. 适用场景

  1. 关联行为场景,需要注意的是,关联行为是可拆分的,而不是“组合”关系。

    即一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。

  2. 事件多级触发场景。

    即一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。

  3. 跨系统的消息交换场景,如消息队列、事件总线的处理机制。

    (事件总线)一个对象必须通知其他对象,而并不知道这些对象是谁;

    需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

  这些是我在网上乱七八糟看到的适用场景,能理解的很好理解,不能理解的很头疼。比如在看某些源码时,感觉像是observer-pattern,但又感觉不是...

  

3. 类图

涉及到角色:

  • AbstractSubject:抽象主题;
  • Subject:具体主题(被观察者);重点,会用一个集合List|Map|Set来保存所有的观察者。
  • AbstractObserver:抽象观察者;
  • Observer:具体观察者;

  注意:观察者observer与被观察者subject是相对的,某些情况下可能两者都是。

  之所以要定义AbstractSubject、 AbstractObserver,主要在于对开发原则(SOLID)的考虑,解除耦合及方便扩展。

  (看到有blog总结说Observer满足"开闭原则",我对SOLID理解不是那么深,只是有体会应该要依赖抽象/接口,所以才会定义AbstractSubject、 AbstractObserver)。

  特别:因为我定义的是interface AbstractSubject,所以我把observers定义到了Subject。视实际情况可能会定义为abstract class AbstractSubject,然后把observers定义在其中,所有AbstractSubject的子类共享observers.

  

4. 最基本的示例代码

  场景: subject[微信公众号],observer[微信用户]。当公众号更新时,将更新信息[message]通知所有的观察者。

  特别:observer-pattern可以分为"推模型"、"拉模型",示例代码是"推模型"。

/** 抽象主题 */
public interface AbstractSubject {
String name();
void add(AbstractObserver observer);
void del(AbstractObserver observer);
void publishMessage(String message);
}
/** 抽象观察者 */
public interface AbstractObserver {
String name();
void onMessage(AbstractSubject subject, String msg);
}
/** 具体主题(被观察者)*/
public class Subject implements AbstractSubject {
private final Set<AbstractObserver> observers = Sets.newLinkedHashSet();
private final String name; public Subject(String name) {
this.name = name;
} @Override
public String name() {
return name;
} @Override
public void add(AbstractObserver observer) {
observers.add(observer);
} @Override
public void del(AbstractObserver observer) {
observers.remove(observer);
} @Override
public void publishMessage(String message) {
for (AbstractObserver observer : observers){
observer.onMessage(this, message);
}
} }
/** 具体观察者 */
public class Observer implements AbstractObserver {
private final String name; public Observer(String name) {
this.name = name;
} @Override
public String name() {
return name;
} public void onMessage(AbstractSubject subject, String msg) {
System.out.println(String.format("observer[%s]: subject[%s], message[%s]", name, subject.name(), msg));
}
}
public class TestObserver {
public static void main(String[] args) {
AbstractSubject subject = new Subject("公众号-01"); AbstractObserver observer1 = new Observer("user-01");
AbstractObserver observer2 = new Observer("user-02");
AbstractObserver observer3 = new Observer("user-03"); subject.add(observer1);
subject.add(observer2);
subject.add(observer3); subject.publishMessage("some message");
}
}
输出结果:
observer[user-01]: subject[公众号-01], message[some message]
observer[user-02]: subject[公众号-01], message[some message]
observer[user-03]: subject[公众号-01], message[some message]

  

5. 注意事项

  1. (开发效率)虽然observer-pattern代码看起来很简单,但如果用于实际业务逻辑代码,非框架级代码,用observer-pattern会让开发、调试、阅读等内容变得比较复杂。

  2. (运行效率)如果一个subject有很多直接和间接的observer的话,将所有的observers都通知到会花费很多时间。

    解决:

      视具体情况,更多可能采用异步的方式通知各个observer。避免observer之间相互的影响。

      且要注意若通知某个observer发生exception时,subject要怎么处理?是由subject处理还是由observer处理?

  3. (循环依赖)如果在observer和subject之间有循环依赖的话,注意避免循环调用,否则会导致系统崩溃。

  4. (缺点)observer-pattern没有相应的机制让observer知道subject是怎么发生变化的,而仅仅只是知道subject发生了变化。

      个人并不理解,感觉有办法能让object知道subject是怎么发生变化的啊。

  5. 抉择:是用"推模式",还是"拉模式"。

6. "推模型"、"拉模型"

个人认同的"推模型"、"拉模型"说法:

  • "推模型":subject维护一份observers的列表,每当有更新发生,subject会把需要的参数主动推送到各个observer。

  • "拉模型": observer维护自己所关心的subjects列表,自行决定在合适的时间(延时性)去subjects获取相应的更新数据。

  我最初也没扯清楚"推模型"、"拉模型"的区别。然后去看了几篇blog,发现有两种说法,有种说法跟我最初理解的一样,但当我看见另外一种说法时,我发现更能接受"另外"的这种说法。

  "推模型"、"拉模型"的区别是:如果subject是主动的一方,则是"推模型";反之,若observer是主动方,则是"拉模型"。

  "推模型",如上面代码示例,是由subject维护了一份observers,由subject主动把消息推送给observer。

  而如果是"拉模型",则是反过来,由observer维护一份subjects,并且是有observer定时去获取subject的信息。

  

(两种说法的参考blog)  

  1. 个人认为:错误的理解

    参考:http://blog.csdn.net/happyever2012/article/details/44678595

      这边文章中所的"推"、"拉"跟我最初理解的一样, 但我一直感觉他们实质都是一样的。

    当subject通知observer时,不管传需求的部分参数,还是把subject本身传递给observer,感觉都像"推"。所以,我就继续在网上找别的blog,找到了我认同的说法。

  2. 个人认为:正确的理解

    参考:http://raychase.iteye.com/blog/1337015

6.1. "推模型"、"拉模型"的各自特点

"推模型"的特点:

  1) 精准。如果没有更新发生,不会有任何更新消息推送的动作,即每次消息推送都发生在确确实实的更新事件之后。

  2) 实时。事件发生后的第一时间即可触发通知操作。

  3) 可以表达出不同事件发生的先后顺序。保证了是按事件改变顺序通知observer。

  4) 因为发起方是subject,所以要在subject方指定通知的时间,避开subject自身或observer方的繁忙时段。

"拉模型"的特点:

  1) 延时性。因为是定时去请求subject的信息,所以有延时性。

  2) (不知道怎么描述)同样因为定时,如果长时间subject没变化,observer还是会按时去更新subject,做是否更新判断,无意义的请求获取subject。

  3) 因为发起方是observer,所以要在observer方指定更新的时间,避开observer自身或subject方的繁忙时段。

  

  这里先要明白一个概念,subject一般被认为是服务端,observer则是客户端。所以有些可能subject与observer根本不是同一个服务(不在同一套代码内)。

  所以,如果需由observer自己控制自己的更新时间,那可以考虑选择"拉模型"。反之,若subject觉得这时间段subject服务器压力太大,又不是一定要马上通知各个observer,则"推模式"更容易在subject控制更新。

6.2. "推模型"、"拉模型"注意事项

  1) 均无法保证事件都会响应

    "推模型"中,若当subject通知observer时,某个observer客户端已经崩溃,那么要考虑subject服务端是不是要记录这次失败,重新通知这个observer直到成功。

    "拉模型"中,因为延时性,subject快速的状态改变1 -> 2 -> 3,observer定时获取的值是3,那么要考虑observer是不是要获取完整的状态变化过程执行相应的逻辑代码。

    

  2) "权限控制"交给谁?

    (这可能无关"推模型"、"拉模型") 比如,"推模式"subject会把所有的触发事件都通知给全部observers,至于这个事件各个observer是不是需要处理,各个observer自己去考虑。(当然,代码完全可以写成是在服务端subject控制某个事件不通知某些observer)。

    可能类似的场景如,subject服务端提供了很多免费的服务,observer客户端要用哪些自己考虑。subject服务端提供了一些收费服务,此时不管是"推模型"还是"拉模型"肯定都只能在subject服务端控制这个权限。

  3) 压力交给谁去承受?

    比如,若observers数据量相当庞大,subject维护这份数据压力很大(或全部通知到压力很大),则可以把关系解脱到observer去完成,即选择"拉模型"。

    又或者,subject服务端觉得被一堆observer定时请求压力较大,则选择"推模型"。

    

  以上很多都可能是我多此一举的瞎考虑, 其实这点性能差别根本不用考虑。

  暂时感觉比较明显的一点,是不是要保证实时性。

  实际中我看的较多都是"推模型","拉模型"少很多。比较明显的"拉模型"就是互联网页面的访问,

7. 扩展: 观察者模式 vs 责任链模式

  暂时还没有仔细看"责任链模式",而且只是个人感觉"责任链模式"和"观察者模式"很相似,都是基于"因为某个东西被触发,所以需要执行操作"。只是"责任链模式"更具体,"观察者模式"的使用场景更抽象。

参考:

  1. https://www.cnblogs.com/cbf4life/archive/2009/12/29/1634773.html

    以上blog并没有让我明白他们之间的区别。

  2. http://bbs.csdn.net/topics/330020444

    可以参考,最后提到的"推模型"与"拉模型"比较有意义。但其余的回答我还是没理解。

8. 实际场景: Redisson源码中的observer-pattern(publish/subscribe)

  场景简述:redisson是用redis实现分布式并发锁,当多个线程竞争同一把锁时,只会有一个线程会获得锁,此时其余线程都在等待获取锁。当持有锁线程执行完并释放锁时,需要通知其余线程去竞争获取锁。此时就是可以用publish/subscribe。

  线程A先去尝试获取锁,发现已被其它线程X持有,此时线程A就subscribe竞争该锁的subject。当线程X释放锁时,及时publish通知所有的observer去竞争锁。(实际redisson中并没有通知全部的observer,而是只通知队列中的第一个竞争线程,让其获得锁。)

  

  本来打算把redisson的相关源码贴出来,但略多,而且要解释的太多了。等写redisson源码阅读再说吧。

【pattern】设计模式(3) - Observer观察者模式的更多相关文章

  1. 设计模式(19)--Observer(观察者模式)--行为型

    作者QQ:1095737364    QQ群:123300273     欢迎加入! 1.模式定义: 观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式.模型-视图( ...

  2. 设计模式 ( 十六 ) 观察者模式Observer(对象行为型)

    设计模式 ( 十六 ) 观察者模式Observer(对象行为型) 1.概述 一些面向对象的编程方式,提供了一种构建对象间复杂网络互连的能力.当对象们连接在一起时,它们就可以相互提供服务和信息. 通常来 ...

  3. 设计模式18:Observer 观察者模式(行为型模式)

    Observer 观察者模式(行为型模式) 动机(Motivation) 在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系”——一个对象(目标对象)的状态发生改变,所有依赖对象(观察者对象) ...

  4. java设计模式解析(1) Observer观察者模式

      设计模式系列文章 java设计模式解析(1) Observer观察者模式 java设计模式解析(2) Proxy代理模式 java设计模式解析(3) Factory工厂模式 java设计模式解析( ...

  5. C++设计模式-Observer观察者模式

    Observer观察者模式作用:观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己 UML图: S ...

  6. 设计模式学习之“观察者模式” [C#]

    <深入浅出设计模式>学习笔记第二章 需求: 开发一套气象监测应用,如图: 气象站,目前有三种装置,温度.湿度和气压感应装置. WeatherData对象追踪气象站的数据,并更新到布告板,布 ...

  7. Observer 观察者模式

    简介       观察者模式(Observer),有时又被称为[发布]publish-[订阅]Subscribe模式.模型-视图(View)模式.源-收听者(Listener)模式或从属者模式.在此种 ...

  8. Java设计模式之《观察者模式》及应用场景

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6513651.html 观察者模式,又可以称之为发布-订阅模式,观察者,顾名思义,就是一个监 ...

  9. 委托、事件、Observer观察者模式的使用解析二

    一.设计模式-Observer观察者模式 Observer设计模式是为了定义对象间的一种一对多的依赖关系,以便于当一个对象的状态改变时,其他依赖于它的对象会被自动告知并更新.Observer模式是一种 ...

  10. Observer观察者模式与OCP开放-封闭原则

    目录 场景引入 在联网坦克项目中使用观察者模式 总结 在学习Observer观察者模式时发现它符合敏捷开发中的OCP开放-封闭原则, 本文通过一个场景从差的设计开始, 逐步向Observer模式迈进, ...

随机推荐

  1. CF-528D Fuzzy Search(FFT字符串匹配)

    Fuzzy Search 题意: 给定一个模式串和目标串按下图方式匹配,错开位置不多于k 解题思路: 总共只有\(A C G T\)四个字符,那么我们可以按照各个字符进行匹配,比如按照\(A\)进行匹 ...

  2. CTF--HTTP服务--命令执行(使用集成工具测试)

    开门见山 1. 扫描靶机ip,发现PCS 192.168.1.158 2. 用sparta工具对靶机进行信息探测 3. 扫描结果 4. 在浏览器中查看80端口的页面 5. 查看网站信息 6. 暴力破解 ...

  3. tensorflow 案例

    import tensorflow as tf import numpy as np #添加一层inputs输入的数据,in_size为输入节点数,out_size为输出节点数,下一个为激励函数 de ...

  4. apache 目录网站显示indexs

    Apache Options Indexes FollowSymLinks详解 第一种方法 <Directory "E:\myvirtualhost\localhost"&g ...

  5. springboot打印sql语句及执行时间

    有时候我们程序的接口比较耗时,需要优化,这时我们可能需要了解该接口执行了哪些sql语句以及耗时 1.引入jar包 <!--监控sql日志--> <dependency> < ...

  6. Linux后门的几种姿势

    转载自 https://evilanne.github.io/2017/08/26/Linux%E5%90%8E%E9%97%A8-%E6%8C%81%E7%BB%AD%E5%85%B3%E6%B3% ...

  7. Linux文件和目录的属性及权限总结

    本文讲述的是文件或目录的属性及权限,比如索引节点inode.文件类型.文件权限及属主:还对setuid.setgid及粘贴位进行了相关的讲解.其中,对ln.chmod.chown.chgrp.umas ...

  8. [python]bytes和str

    Python 3.6.1 (v3.6.1:69c0db5050, Mar 21 2017, 01:21:04) [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] ...

  9. CCF_ 201403-3 _命令行选项

    不要怀疑,这题跟CCF_201604-3_路径解析一样恶心,很多中情况,要仔细读题,注意细节. 写的比较乱. #include<iostream> #include<cstdio&g ...

  10. 通俗易懂的ref和out区别

    ref 和 out 是C#开发中经常用到的两个关键字,但是很多人没有搞清楚这两个关键字的具体区别,下面我们来说一下这两个关键的区别. 零. ref 与 out 的异同 相同: 都是按地址传递: 使用后 ...