【pattern】设计模式(3) - Observer观察者模式
源码地址:https://github.com/vergilyn/design-patterns
另外一个大神很全的Github:https://github.com/iluwatar/java-design-patterns
1. 概述
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当这个主题对象在状态发生变化时,可以通知所有观察者对象,使它们(观察者对象)能够自动更新自己。
比如,微信用户就是观察者,微信公众号就是被观察者(主题对象),当公众号更新时会通知所有的观察者。
以上示例更形象的表述可能是:发布/订阅(publish/subscribe)。
(个人觉得)观察者模式的核心:其实就是触发及响应。所以感觉跟"责任链模式"有一定程度的类似,可能"责任链模式"的适用场景更加具体,而"观察者模式"相对就抽象很多。
2. 适用场景
关联行为场景,需要注意的是,关联行为是可拆分的,而不是“组合”关系。
即一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。事件多级触发场景。
即一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。跨系统的消息交换场景,如消息队列、事件总线的处理机制。
(事件总线)一个对象必须通知其他对象,而并不知道这些对象是谁;
需要在系统中创建一个触发链,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. 注意事项
(开发效率)虽然observer-pattern代码看起来很简单,但如果用于实际
业务逻辑代码,非框架级代码
,用observer-pattern会让开发、调试、阅读等内容变得比较复杂。(运行效率)如果一个subject有很多直接和间接的observer的话,将所有的observers都通知到会花费很多时间。
解决:
视具体情况,更多可能采用异步
的方式通知各个observer。避免observer之间相互的影响。
且要注意若通知某个observer发生exception时,subject要怎么处理?是由subject处理还是由observer处理?(循环依赖)如果在observer和subject之间有循环依赖的话,注意避免循环调用,否则会导致系统崩溃。
(缺点)observer-pattern没有相应的机制让observer知道subject是怎么发生变化的,而仅仅只是知道subject发生了变化。
个人并不理解,感觉有办法能让object知道subject是怎么发生变化的啊。抉择:是用"推模式",还是"拉模式"。
6. "推模型"、"拉模型"
个人认同的"推模型"、"拉模型"说法:
"推模型":subject维护一份observers的列表,每当有更新发生,subject会把需要的参数主动推送到各个observer。
"拉模型": observer维护自己所关心的subjects列表,自行决定在合适的时间(延时性)去subjects获取相应的更新数据。
我最初也没扯清楚"推模型"、"拉模型"的区别。然后去看了几篇blog,发现有两种说法,有种说法跟我最初理解的一样,但当我看见另外一种说法时,我发现更能接受"另外"的这种说法。
"推模型"、"拉模型"的区别是:如果subject是主动的一方,则是"推模型";反之,若observer是主动方,则是"拉模型"。
"推模型",如上面代码示例,是由subject维护了一份observers,由subject主动把消息推送给observer。
而如果是"拉模型",则是反过来,由observer维护一份subjects,并且是有observer定时去获取subject的信息。
(两种说法的参考blog)
个人认为:错误的理解
参考:http://blog.csdn.net/happyever2012/article/details/44678595
这边文章中所的"推"、"拉"跟我最初理解的一样, 但我一直感觉他们实质都是一样的。
当subject通知observer时,不管传需求的部分参数,还是把subject本身传递给observer,感觉都像"推"。所以,我就继续在网上找别的blog,找到了我认同的说法。个人认为:正确的理解
参考: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观察者模式的更多相关文章
- 设计模式(19)--Observer(观察者模式)--行为型
作者QQ:1095737364 QQ群:123300273 欢迎加入! 1.模式定义: 观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式.模型-视图( ...
- 设计模式 ( 十六 ) 观察者模式Observer(对象行为型)
设计模式 ( 十六 ) 观察者模式Observer(对象行为型) 1.概述 一些面向对象的编程方式,提供了一种构建对象间复杂网络互连的能力.当对象们连接在一起时,它们就可以相互提供服务和信息. 通常来 ...
- 设计模式18:Observer 观察者模式(行为型模式)
Observer 观察者模式(行为型模式) 动机(Motivation) 在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系”——一个对象(目标对象)的状态发生改变,所有依赖对象(观察者对象) ...
- java设计模式解析(1) Observer观察者模式
设计模式系列文章 java设计模式解析(1) Observer观察者模式 java设计模式解析(2) Proxy代理模式 java设计模式解析(3) Factory工厂模式 java设计模式解析( ...
- C++设计模式-Observer观察者模式
Observer观察者模式作用:观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己 UML图: S ...
- 设计模式学习之“观察者模式” [C#]
<深入浅出设计模式>学习笔记第二章 需求: 开发一套气象监测应用,如图: 气象站,目前有三种装置,温度.湿度和气压感应装置. WeatherData对象追踪气象站的数据,并更新到布告板,布 ...
- Observer 观察者模式
简介 观察者模式(Observer),有时又被称为[发布]publish-[订阅]Subscribe模式.模型-视图(View)模式.源-收听者(Listener)模式或从属者模式.在此种 ...
- Java设计模式之《观察者模式》及应用场景
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6513651.html 观察者模式,又可以称之为发布-订阅模式,观察者,顾名思义,就是一个监 ...
- 委托、事件、Observer观察者模式的使用解析二
一.设计模式-Observer观察者模式 Observer设计模式是为了定义对象间的一种一对多的依赖关系,以便于当一个对象的状态改变时,其他依赖于它的对象会被自动告知并更新.Observer模式是一种 ...
- Observer观察者模式与OCP开放-封闭原则
目录 场景引入 在联网坦克项目中使用观察者模式 总结 在学习Observer观察者模式时发现它符合敏捷开发中的OCP开放-封闭原则, 本文通过一个场景从差的设计开始, 逐步向Observer模式迈进, ...
随机推荐
- CF-528D Fuzzy Search(FFT字符串匹配)
Fuzzy Search 题意: 给定一个模式串和目标串按下图方式匹配,错开位置不多于k 解题思路: 总共只有\(A C G T\)四个字符,那么我们可以按照各个字符进行匹配,比如按照\(A\)进行匹 ...
- CTF--HTTP服务--命令执行(使用集成工具测试)
开门见山 1. 扫描靶机ip,发现PCS 192.168.1.158 2. 用sparta工具对靶机进行信息探测 3. 扫描结果 4. 在浏览器中查看80端口的页面 5. 查看网站信息 6. 暴力破解 ...
- tensorflow 案例
import tensorflow as tf import numpy as np #添加一层inputs输入的数据,in_size为输入节点数,out_size为输出节点数,下一个为激励函数 de ...
- apache 目录网站显示indexs
Apache Options Indexes FollowSymLinks详解 第一种方法 <Directory "E:\myvirtualhost\localhost"&g ...
- springboot打印sql语句及执行时间
有时候我们程序的接口比较耗时,需要优化,这时我们可能需要了解该接口执行了哪些sql语句以及耗时 1.引入jar包 <!--监控sql日志--> <dependency> < ...
- 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% ...
- Linux文件和目录的属性及权限总结
本文讲述的是文件或目录的属性及权限,比如索引节点inode.文件类型.文件权限及属主:还对setuid.setgid及粘贴位进行了相关的讲解.其中,对ln.chmod.chown.chgrp.umas ...
- [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)] ...
- CCF_ 201403-3 _命令行选项
不要怀疑,这题跟CCF_201604-3_路径解析一样恶心,很多中情况,要仔细读题,注意细节. 写的比较乱. #include<iostream> #include<cstdio&g ...
- 通俗易懂的ref和out区别
ref 和 out 是C#开发中经常用到的两个关键字,但是很多人没有搞清楚这两个关键字的具体区别,下面我们来说一下这两个关键的区别. 零. ref 与 out 的异同 相同: 都是按地址传递: 使用后 ...