一:观察者模式简介

二:jdk实现观察者模式的源码

三:实际例子

四:观察者模式的优点和不足

五:总结

一:观察者模式简介

有时又被称为发布(publish )-订阅(Subscribe)模式、模型-视图(View)模式、源-收听者(Listener)模式或从属者模式)是软件设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。这段话引自百度百科,粗看很懵。在观察者模式中,分为两个主体对象,一个是观察者,一个是被观察者。他们之间是多对一的关系,被观察者会对观察者进行注册操作,当它的自身状态发生改变的时候,会通知(notify)观察者这一消息,观察者收到通知会进行自身的更新。这个其实不难理解,举个简单的例子,比如我们去追一部剧,在youku网上看,它底下会有一个订阅的按钮。当我们点击了之后,以后有新的剧集出来都会通知我们。这就是一个典型的观察者模式,其中电视剧是被观察者,用户是观察者。

二:jdk实现观察者模式的源码

2.1:被观察者源码

public class Observable {
private boolean changed = false;//是否被改变
private Vector obs;//维持一个Vector集合用来装观察者对象,注意Vector是线程安全的
public Observable() {//构造方法
obs = new Vector();//新创建一个装有观察者的集合
} public synchronized void addObserver(Observer o) {//添加观察者
if (o == null)//如果是null
throw new NullPointerException();//抛出空指针异常
if (!obs.contains(o)) { //如果不包含该观察者对象
obs.addElement(o);//把观察者对象添加入集合
}
} public synchronized void deleteObserver(Observer o) {//删除观察者对象
obs.removeElement(o);//调用vector的removeElement方法把观察者移除出去
} public void notifyObservers() {//通知所有的观察者更新
notifyObservers(null);//调用notifyObservers方法,传入一个null参数
} public void notifyObservers(Object arg) {//通知所有的观察者 Object[] arrLocal;//声明一个方法内部对象数组 synchronized (this) {//给当前的类实例上锁 if (!changed)//如果没有发生变化
return;//直接退出方法
arrLocal = obs.toArray();//将集合转化为数组赋给声明的对象数组
clearChanged();//调用clearChanged方法清除变化
} for (int i = arrLocal.length-1; i>=0; i--)//倒序遍历整个数组
((Observer)arrLocal[i]).update(this, arg);//调用观察者的update方法
} 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();//返回观察者的数量
}
}

其中可以看出被观察者内部维持着一个线程安全的vector,用来存放观察者,观察者可以有好多个,他们之间是一对多的关。被观察者可以对观察者进行注册、删除、通知、计数等操作。可以看出每个方法上面都添加了一个synchronized,并且基本上都是方法级别的,锁住当前的方法,这是考虑到线程安全问题。可能有很多个观察者同时对一个被观察者进行操作,防止线程之间出现的数据脏读等问题都采用了加锁的手段。

重点分析一下notifyObservers()方法:

public void notifyObservers(Object arg) {//通知所有的观察者

    Object[] arrLocal;//声明一个方法内部对象数组

    synchronized (this) {//给当前的类实例上锁

        if (!changed)//如果没有发生变化
return;//直接退出方法
arrLocal = obs.toArray();//将集合转化为数组赋给声明的对象数组
clearChanged();//调用clearChanged方法清除变化
} for (int i = arrLocal.length-1; i>=0; i--)//倒序遍历整个数组
((Observer)arrLocal[i]).update(this, arg);//调用观察者的update方法
}

这里采用了内部声明的对象数组的方法,在调用update方法的时候并没有上锁,因为vector是全局的,为什么要这么做呢?不会存在线程安全隐患吗?这里这个对象数组的好处就直接体现出来了,先看看这个方法的注释:

   We don't want the Observer doing callbacks into arbitrary code while holding its own Monitor. The code where we extract each Observable from    the Vector and store the state of the Observer
needs synchronization, but notifying observers does not (should not). The worst result of any potential race-condition here is that:
1) a newly-added Observer will miss a notification in progress
2) a recently unregistered Observer will be wrongly notified when it doesn't care

综合注释来讲,这个方法注意点有以下5个:

①:如果该方法加锁,假如一个被观察者的观察者数量比较多的时候,那么进行依次遍历通知将会是一个非常耗时耗资源的操作。因为其它线程都将在这里阻塞,无法进入,只能等它完了 才能获取锁再进行通知。

②:arrLocal是方法内部变量,我们都知道内部变量存放在栈中,是私有的,也就是其它线程并不会影响这里。在进行集合转数组这块,是同步加锁的。其他线程不会访问到synchronized方法

③:arrLocal相当于对所有的观察者进行了一个缓存,如果不加缓存会怎样?不加缓存的话,如果一个线程添加观察者,一个线程删除观察者,那么vector将会出现ConcurrenModifyException,修改并发异常,并且很有可能出现空指针异常。

④:利用集合转换数组的方法可以把观察者保存起来,进行独立的操作,这部分数据的更新是完全不依赖于外部变化的。有这样一个问题,假如现在一个线程正在进行notifyObservers方法调用,而其他线程调用了deleteObserver()方法,这时候被删除的observer还会接受到通知吗?答案是不会,这也是这种缓存机制的缺点。一方面新添加的观察者不会立刻得到通知(除非在添加完立刻有线程执行了obs.toArray()方法).另一方面,新删除的观察者可能不会立刻停止通知,这也是设计上的不可避免的问题,期待后期jdk有解决的好方案。

⑤:jdk的设计者特意选择了vector这个线程安全的集合,而不是ArrayList、LinkedList等,在进行obs.toArray()的时候,其它线程无法访问obs,这个时候obs是不受影响的。

2.2:观察者源码

package java.util;

public interface Observer {
void update(Observable var1, Object var2);
}

相比之下,观察者就简单多了,本身是一个接口,就一个update方法进行数据的更新,传入的参数为被观察者和需要传递的参数。

三:实际例子

我们用代码来模拟一下这个场景:优酷上提供了电视剧频道的订阅按钮,在用户点击了订阅以后,在集数进行更新的时候会通知用户,这里就是一个典型的观察者模式的模型。如下图是军师联盟的订阅页面~

3.1:被观察者

public class TvPlay extends Observable{

    private String name;

    private int episodes;

    public TvPlay(String name, int episodes) {
super();
this.name = name;
this.episodes = episodes;
} public TvPlay(String name) {
super();
this.name = name;
} public void registerWatcher(Observer observer){ if (observer instanceof Watcher) { Watcher watcher=(Watcher)observer; addObserver(watcher); System.out.println("欢迎您"+watcher.getName()+"关注"+getName()); }
} public void cancelWatcher(Observer observer){ if (observer instanceof Watcher) { Watcher watcher=(Watcher)observer; deleteObserver(watcher); System.out.println(watcher.getName()+"已取消"+getName()+"订阅");
} } public void notifyWatchers(){ setEpisodes(20); notifyObservers(getEpisodes()); } public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getEpisodes() {
return episodes;
} public void setEpisodes(int episodes) {
this.setChanged();
this.episodes = episodes;
} }

3.2:观察者

public class Watcher implements Observer{

    private String name;

    public Watcher(String name) {
super();
this.name = name;
} @Override
public void update(Observable o, Object arg) { int episodes=(int)arg;//集数 if (o instanceof TvPlay) { TvPlay tvPlay=(TvPlay)o; System.out.println(getName()+"您好,"+tvPlay.getName()+"已更新到"+episodes+"集"); } } public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} }

3.3:测试类

public class Test {

    public static void main(String[] args) {

        Watcher watcher1 = new Watcher("张晓");

        Watcher watcher2 = new Watcher("张化");

        Watcher watcher3 = new Watcher("张军");

        TvPlay tvPlay = new TvPlay("大军师司马懿电视剧");

        tvPlay.registerWatcher(watcher1);

        tvPlay.registerWatcher(watcher2);

        tvPlay.registerWatcher(watcher3);

        tvPlay.cancelWatcher(watcher3);

        tvPlay.notifyWatchers();

        System.out.println("一共有"+tvPlay.countObservers()+"名关注者");

    }

}
欢迎您张晓关注大军师司马懿电视剧!
欢迎您张化关注大军师司马懿电视剧!
欢迎您张军关注大军师司马懿电视剧!
张军已取消大军师司马懿电视剧订阅
张化您好,大军师司马懿电视剧已更新到20集
张晓您好,大军师司马懿电视剧已更新到20集
一共有2名关注者

上面的例子简单实现了观察者模式,主要体会它的设计思路以及应用范围。

四:观察者模式的优势和不足

4.1、观察者模式在被观察者和观察者之间建立一个抽象的耦合。被观察者角色所知道的只是一个具体观察者列表,每一个具体观察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体观察者,它只知道它们都有一个共同的接口。

4.2:被观察者和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。如果被观察者和观察者都被扔到一起,那么这个对象必然跨越抽象化和具体化层次。4.2、观察者模式支持广播通讯。被观察者会向所有的登记过的观察者发出通知

观察者模式有下面的缺点:

4.3:如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间,这点在上面的notifyObservers方法中已经提到

4.4:如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察者模式是要特别注意这一点。

4.5:如果对观察者的通知是通过另外的线程进行异步投递的话,系统必须保证投递是以自恰的方式进行的。

4.6::虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎么发生变化的

五:总结

观察者模式是一个使用非常频繁的模式,在基于事件模型中,事件源发生变化,那么会通知事件影响者发生也发生变化。本篇博客总结了观察者模式的使用方法,探究了jdk的中的源码,在学习了优秀的源码之后,进行了一个小例子的探究,旨在研究观察者模式的用法。并总结了它的优势和不足,在实际项目中注意体会观察者模式的设计思想,并加入到自己的项目中去,充分学习这一设计模型。

观察者模式—jdk自带源码分析的更多相关文章

  1. shiro实现无状态的会话,带源码分析

    转载请在页首明显处注明作者与出处 朱小杰      http://www.cnblogs.com/zhuxiaojie/p/7809767.html 一:说明 在网上都找不到相关的信息,还是翻了大半天 ...

  2. ConcurrentHashMap JDK 1.6 源码分析

    前言 前段时间把 JDK 1.6 中的 HashMap 主要的一些操作源码分析了一次.既然把 HashMap 源码分析了, 就顺便把 JDK 1.6 中 ConcurrentHashMap 的主要一些 ...

  3. JDK集合框架源码分析 - 简单概要

    1.类继承体系 在集合框架的类继承体系中,最顶层有两个接口Collection.Map: Collection 表示一组纯数据 Map 表示一组key-value对 Collection的类继承体系: ...

  4. 设计模式(十八)——观察者模式(JDK Observable源码分析)

    1 天气预报项目需求,具体要求如下: 1) 气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方). 2) 需要设计开放型 API,便于其他第三方也能接入气象 ...

  5. JDK源码分析(6)ConcurrentHashMap

    JDK版本 ConcurrentHashMap源码分析 table:默认为null,初始化发生在第一次插入操作,默认大小为16的数组,用来存储Node节点数据,扩容时大小总是2的幂次方. nextTa ...

  6. 设计模式(一)——Java单例模式(代码+源码分析)

    1)单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能 2)当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不 ...

  7. 设计模式(九)——装饰者模式(io源码分析)

    1 星巴克咖啡订单项目(咖啡馆): 1) 咖啡种类/单品咖啡:Espresso(意大利浓咖啡).ShortBlack.LongBlack(美式咖啡).Decaf(无因咖啡) 2) 调料:Milk.So ...

  8. JDK 自带的观察者模式源码分析以及和自定义实现的取舍

    前言 总的结论就是:不推荐使用JDK自带的观察者API,而是自定义实现,但是可以借鉴其好的思想. java.util.Observer 接口源码分析 该接口十分简单,是各个观察者需要实现的接口 pac ...

  9. JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue

    JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue 目的:本文通过分析JDK源码来对比ArrayBlockingQueue 和LinkedBlocki ...

随机推荐

  1. Hive_UDF函数中集合对象初始化的注意事项

    UDF函数中定义的集合对象何时初始化 udf函数放在sql中对某个字段进行处理,那么在底层会创建一个该类的对象,这个对象不断的去调用这个evaluate(...)方法,截图如下:   1.1 如果说对 ...

  2. 修改nopCommerce中的实体

                               对已有实体增加一个属性(对Category增加一个SomeNewProperty)   最近在研究nopcommerce,这里是对官网上文档的学习 ...

  3. JavaSE笔记-泛型

    定义带泛型的类 public class Cat<T> { //可以用T定义实例变量 private T name; //可以用T定义形参 //构造器没有<> public C ...

  4. 注释中不允许出现字符串 "--"。

    问题: 在启动tomcat时会出现如上错误,同时有可能会出现xml无法解析等错误 解决办法: 注释中不能出现字符串 "--",即需要把xml文件中多余的“--”去掉,例如: < ...

  5. 记录linux tty的一次软锁排查

    本过程参照了某大侠的https://github.com/w-simon/debug/blob/master/tty_lock_cause_sytemd_hung , 当第二次出现的时候,还是排查了一 ...

  6. 如何在同一台机器上安装多个MySQL的实例

    转自:'http://www.cnblogs.com/shangzekai/p/4375271.html 最近由于工作的需要,需要在同一台机器上搭建两个MySQL的实例,(注:已经存在了一个3306的 ...

  7. elasticsearch安装ik分词器

    一.概要: 1.es默认的分词器对中文支持不好,会分割成一个个的汉字.ik分词器对中文的支持要好一些,主要由两种模式:ik_smart和ik_max_word 2.环境 操作系统:centos es版 ...

  8. java面向对象的三大特性——继承

    ul,ol { margin: 0; list-style: none; padding: 0 } a { text-decoration: none; color: inherit } * { ma ...

  9. CentOS修改Tomcat端口号

    Linux下修改Tomcat默认端口 1.方法一 假设tomcat所在目录为/usr/local/apache-tomcat/ 1.打开tomcat配置文件 # vi /usr/local/apach ...

  10. JDBC (一)

    1 JDBC 简介 sun公司为了简化.统一对数据库的操作,定义了一套java操作数据库的规范,称之为JDBC. 数据库厂商的驱动就是对JDBC的实现. 没有JDBC之前  vs 有JDBC之后 JD ...