文档章节

观察者设计模式

乒乓狂魔
 乒乓狂魔
发布于 2015/02/07 10:37
字数 1715
阅读 161
收藏 2
点赞 0
评论 0
先来看下下报纸和杂志的订阅:
(1)报社:出版报纸和杂志
(2)订阅者:向某家报社订阅报纸和杂志,只要报社出版了新的报纸,订阅者就会收到最新的报纸和杂志。
(3)报社具有添加和删除订阅者的功能(其实应该是订阅者具有订阅和退订的功能,这个主动权应该是订阅者而不是报社,报社也应该对外开放这样的方法)
下面就让我们来简单实现上述的描述:
报社:PublishingHouse 如下:

package com.lg.design.obser;

import java.util.ArrayList;
import java.util.List;

public class PublishingHouse {
	
	private List<Subscriber> subscribers=new ArrayList<Subscriber>();
	
	public void add(Subscriber subscriber){
		if(subscriber!=null && !subscribers.contains(subscriber)){
			subscribers.add(subscriber);
		}
	}
	
	public void delete(Subscriber subscriber){
		if(subscriber!=null){
			subscribers.remove(subscriber);
		}
	}

	public void produceNewspaper(String newspaper) {
		notifySubscribers(newspaper);
	}

	private void notifySubscribers(String newspaper) {
		for(Subscriber subscriber:subscribers){
			subscriber.receiveNewspaper(newspaper);
		}
	}
	
}

PublishingHouse 有添加和删除订阅者Subscriber的方法,同时在生产报纸时来通知所有的订阅者。
订阅者:Subscriber如下:

package com.lg.design.obser;

public interface Subscriber {

	public void receiveNewspaper(String newspaper);
	
}

订阅者是一个接口,订阅到报纸后如何处理不同的订阅者有不同的实现。
如某类订阅者 ASubscriber:

package com.lg.design.obser;

public class ASubscriber implements Subscriber{

	@Override
	public void receiveNewspaper(String newspaper) {
		System.out.println("A receive newspaper:"+newspaper);
	}

}

再如某类订阅者 BSubscriber:
package com.lg.design.obser;

public class BSubscriber implements Subscriber{

	@Override
	public void receiveNewspaper(String newspaper) {
		System.out.println("B receive newspaper:"+newspaper);
	}

}

测试方法如下:
public static void main(String[] args){
		PublishingHouse publishingHouse=new PublishingHouse();
		Subscriber a=new ASubscriber();
		Subscriber b=new BSubscriber();
		publishingHouse.add(a);
		publishingHouse.add(b);
		
		publishingHouse.produceNewspaper("第一天的报纸");
		publishingHouse.produceNewspaper("第二天的报纸");
	}

输出的结果为:
A receive newspaper:第一天的报纸
B receive newspaper:第一天的报纸
A receive newspaper:第二天的报纸
B receive newspaper:第二天的报纸

观察者模式其实很简单,就是报社维护了一个订阅者集合,一旦有新的报纸,就会去遍历那个集合,调用集合里元素的通用方法receiveNewspaper(接口方法)。报社的produceNewspaper方法就是将上述过程进行了封装而已。

上述的例子很简单,同时有很多问题。下面来说说JDK自身已实现的观察者模式:
被观察者Observable(如报社),观察者Observer(如订阅者)
观察者Observer如下:

public interface Observer {
    
    void update(Observable o, Object arg);
}

被观察者Observable如下:
package java.util;
public class Observable {
    private boolean changed = false;
    private Vector obs;

    public Observable() {
        obs = new Vector();
    }

    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }

    public void notifyObservers() {
        notifyObservers(null);
    }

    public void notifyObservers(Object arg) {
        /*
         * a temporary array buffer, used as a snapshot of the state of
         * current Observers.
         */
        Object[] arrLocal;

        synchronized (this) {
            /* 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
             */
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }

        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }
    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集合来存放所有的Observer,具有添加删除Observer的功能。同时还有一个changed属性,用来标示状态是否发生变化,所以在执行notifyObservers通知前要把该状态改为true,为了保证线程安全,setChanged、hasChanged、clearChanged都加上了synchronized 进行同步。再来详细看下notifyObservers过程:
arrLocal作为当时Observers的一个快照,在该方法中对状态的判断和改变进行了同步,然后按照倒序进行了事件的通知,即调用集合中每个Observer。

先说下我对jdk实现的观察者模式的看法:
(1)加入changed属性,则需要在每次想要执行通知前,提前设置setChanged即changed=true,然后执行notifyObservers才有效,这使得我们在使用时必须使setChanged方法和notifyObservers方法进行synchronized操作,使之成为“原子操作”,不然在多线程环境中,会通知失败。如线程1执行setChanged,线程2执行setChanged,线程1执行notifyObservers,它就会把changed=false,此时线程2再执行notifyObservers,就会因为changed=false而直接退出,不会发出通知,changed属性的确给我们带来了同步的麻烦,也体现在了notifyObservers方法中,如下:

public void notifyObservers(Object arg) {
        /*
         * a temporary array buffer, used as a snapshot of the state of
         * current Observers.
         */
        Object[] arrLocal;

        synchronized (this) {
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }

        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }

有了changed属性,必须要在该方法中对changed的判断进行同步,否则会造成如下现象:线程1设置了setChanged方法,在执行notifyObservers时还未执行到clearChanged方法,此时线程2不用调用setChanged方法,直接调用notifyObservers,也会实现通知。
(2)Observer接口的方法,void update(Observable o, Object arg);此时传递的是Observable对象,如MyObservable继承了Observable 对象,此时我们要获取MyObservable的数据就必须要对void update(Observable o, Object arg)传进来的Observable o进行强制类型转换,而我们应该可以利用泛型来更好的设计这一个接口,避免强制类型转换。如下:

public interface MyObserver<T extends Observable>{

	public void update(T t,Object args);
}

(3)对于Observable的notifyObservers通知顺序,默认写死为倒序通知,不应该是这样的,而是,应该对开开放成接口,同时提供多种实现,如正序通知的实现和倒序通知的实现。如果还满足不了需求,我们就可以自定义顺序实现该接口,设置进Observable中。
(4)对于Observable内部使用Vector,也是不可扩展的一个地方,如果我们想换种集合就没法实现,并不是什么需求都会去使用Vector。
(5)观察者自己应该具有取消关注的权利,即微信中的订阅一样,自己有权利去关注和取消关注,所以有时观察者接口由希望有一个取消关注的方法,然而该方法的实现又很明白,不需要观察者自己来实现。这就使用到了java8中接口中默认方法的实现。如下所示:

public interface Observer {
    
    void update(Observable o, Object arg);

    default void unregister(Observable o){
         o.deleteObserver(this);
    }

    default void register(Observable o){
         o.addObserver(this);
    }
}

不知道这样写对不对,
所以对于Jdk自身实现的观察者模式扩展性并不好,同时大量使用synchronized来解决多线程问题,没有更好的使用多线程包中的同步技术。观察者模式本身很简单,所以有时候还是需要我们自己来写一个观察者模式。

以上纯属个人拙见,欢迎激烈讨论,共同进步。

若想转载请注明出处:   http://lgbolgger.iteye.com/blog/2115108
作者:iteye的乒乓狂魔

© 著作权归作者所有

共有 人打赏支持
乒乓狂魔
粉丝 979
博文 105
码字总数 271356
作品 0
长宁
程序员
JavaScript常用设计模式

设计模式 设计模式是一种在长时间的经验与错误中总结出来可服用的解决方案。 设计模式主要分为3类: 创建型设计模式:专注于处理对象的创建 Constructor构造器模式,Factory工厂模式,Singl...

a独家记忆
07/13
0
0
Tomcat 系统架构与设计模式_ 设计模式分析

门面设计模式 门面设计模式在 Tomcat 中有多处使用,在 Request 和 Response 对象封装中、Standard Wrapper 到 ServletConfig 封装中、ApplicationContext 到 ServletContext 封装中等都用到...

lvzjane
2014/11/03
0
0
观察者模式 vs 发布-订阅模式

我曾经在面试中被问道,“观察者模式和发布订阅模式的有什么区别?” 我迅速回忆起“Head First设计模式”那本书: 发布 + 订阅 = 观察者模式 “我知道了,我知道了,别想骗我” 我微笑着回答...

缪宇
06/29
0
0
Spring框架中的设计模式(三)

Spring框架中的设计模式(三) 原创: 瑞查德-Jack 在之前的两篇文章中,我们看到了一些在Spring框架中实现的设计模式。这一次我们会发现这个流行框架使用的3种新模式。 本文将从描述两个创意...

瑞查德-Jack
今天
0
0
设计模式17——Observer设计模式

Observer观察者设计模式用于将对象的变化通知给感兴趣的用户。在Observer模式中的角色为主题(subject)与观察者(observer),观察者订阅它感兴趣的主题,一个主题可以被多个观 察者订阅,当...

小米米儿小
2014/01/26
0
0
《Android深入透析》之常用设计模式经验谈

前言: Android开发的设计模式,基本设计思想源于java的设计模式,java的设计模式有N多种,据不完全统计,迄今为止,网络出现最频繁的大概有23种。Java只是一门开发语言,学会并掌握这门语言...

朵朵和糖糖
2014/11/12
0
8
(六)观察者模式详解(包含观察者模式JDK的漏洞以及事件驱动模型)

本章我们讨论一个除前面的单例以及代理模式之外,一个WEB项目中有可能用到的设计模式,即观察者模式。 说起观察者模式,LZ还是非常激动的,当初这算是第一个让LZ感受到设计模式强大的家伙。当...

Sheamus
2015/01/29
0
0
【软考学习】设计模式——观察者模式

【背景】 设计模式是非常重要的一块知识,每个设计模式都值得深入了解和学习。 【内容】 结构型设计模式总结: 观察者设计模式总结: 一、定义:定义了一种一对多的依赖关系,让多个观察者对...

yym15732626210
01/31
0
0
Cocos2d-x设计模式--二段构建模式

设计模式在程序设计中会经常用到,也许你从来没有留意过设计模式,其实你却一直在使用设计模式!cocos2dx中有不少的设计模式,所以从本篇博客开始探讨一下cocos2dx中的设计模式,看看引擎都使...

_子墨
2014/09/15
0
0
ListView 数据与UI更新机制之观察者模式

之前有一篇文章专门介绍观察者模式知识,当时通过EventBus来进行分析。近日在读《Android源码设计模式解析与实战》,看到书中介绍ListView中使用到观察者模式。为了加深对观察者模式的理解,...

Tifkingsly
07/13
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Git 基础 - 远程仓库的使用

远程仓库的使用 要参与任何一个 Git 项目的协作,必须要了解该如何管理远程仓库。远程仓库是指托管在网络上的项目仓库,可能会有好多个,其中有些你只能读,另外有些可以写。同他人协作开发某...

谢思华
12分钟前
0
0
面试宝典-悲观锁和乐观锁

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。 乐观锁(Optimistic...

suyain
13分钟前
0
0
崛起于Springboot2.X之集成MongoDb使用mongoTemplate CRUD(27)

1、pom依赖 <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.7</version></dependency><dependency> <groupId>log4j</......

木九天
24分钟前
0
0
切分log日志

新建logback.xml放到resource里面 <?xml version="1.0" encoding="utf-8"?><configuration> <appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender"> <......

talen
29分钟前
0
0
spring @Resource 和 @Autowired 的使用区别

这两个 注解 @Resource 和 @Autowired , 常识都知道 @Resource 是 JAVAEE 自带的,@Autowired 是 spring 的自定义注解。 一般情况下, 使用 bean的时候, 这两个注解 随便使用一个即可。 但...

之渊
34分钟前
0
0
springboot集成elasticsearch客户端问题记录

1背景说明 服务端ES版本为5.5.2,springboot版本为1.5.6。 工程中添加如下依赖 2问题记录 2.1 NetworkPlugin类找不到 报错java.lang.ClassNotFoundException: org.elasticsearch.plugins.Net...

zjg23
36分钟前
1
0
快速构建ceph可视化监控系统

前言 ceph的可视化方案很多,本篇介绍的是比较简单的一种方式,并且对包都进行了二次封装,所以能够在极短的时间内构建出一个可视化的监控系统 本系统组件如下: ceph-jewel版本 ceph_expor...

万建宁
36分钟前
0
0
Java构造器使用注意

public class 父类A {int age = 10;protected void say() {System.out.println("父类A");}public 父类A() {override();}public void override() {Syst...

咸鱼AI
36分钟前
0
0
TensorFlow 线性分类

构造直线 z = 2 * x - 3 * y + 4 x0*w0+x1*w1+b=0 x1=-x0* w0/w1-b/w1 斜率 k= -w0/w1 截距 -b/w1 随机生成数据,加入一定的偏差,用直线将二维平面分为两部分 使用线性模型拟合参数 损失函数...

阿豪boy
39分钟前
0
0
翻译冒泡排序测试

翻译一个冒泡排序: var a = [1,3,2,4,6,5];var f = 0;var n = a.length ;for( var i =1; i<= n; i++) { for( var j = n-1 ; j >= i; j --) { if(a[j] < a[j+1]) { ......

钟元OSS
41分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部