设计模式之16观察者模式(笔记)

来源:互联网 发布:数据库数字类型 编辑:程序博客网 时间:2024/04/29 11:41

1 定义:Observer Pattern,也叫做发布订阅模式(Publish / subscribe

1.1 定义:Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.(定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。)

1.2 通用类图:

1.3 通用代码:

public abstract class Subject {// 定一个一个观察者数组private Vector<Observer> obsVector = new Vector<Observer>();// 增加一个观察者public void addObserver(Observer o) {this.obsVector.add(o);}// 删除一个观察者public void delObserver(Observer o) {this.obsVector.remove(o);}// 通知所有观察者public void notifyObserver() {for (Observer o : this.obsVector) {o.update();}}}public interface Observer {// 更新方法public void update();}public class ConcreteSubject extends Subject {// 具体的业务public void doSomething() {/* * do something */super.notifyObserver();}}public class ConcreteObserver implements Observer {// 实现更新方法public void update() {System.out.println("接收到信息,并进行处理!");}}public class Test {public static void main(String[] args) {// 创建一个被观察者ConcreteSubject subject = new ConcreteSubject();// 定义一个观察则Observer obs = new ConcreteObserver();// 观察者观察被被观察则subject.addObserver(obs);// 观察者开始活动了subject.doSomething();}}


2 优点

2.1 观察者与被观察者之间是抽象耦合:因此无论是增加观察者还是增加被观察者,都十分容易。

2.2 可以经过扩展,从而建立一条链状的触发机制。

3 缺点

3.1 Java中消息的通知默认是顺序执行,一个观察者卡壳,会影响整体的执行效率。这时可以考虑采用异步的方式。(多线程呗)

3.2 多级触发时的效率更让人担忧,设计时需考虑。(就是上面所说的链状触发)

4 应用场景

4.1 使用关联行为的场景。注意是可拆分的关联,而非组合。

4.2 事件多级触发场景。

4.3 跨系统的消息交换场景,如消息队列的处理机制。(如kkPlayer的全局快捷键)

5 注意事项

5.1 广播链的问题:在一个观察者模式中最多出现一个对象既是观察者又是被观察者,消息转发最多一两次,还比较好控制。

5.2 异步处理问题:需要考虑线程安全和队列的问题。(可以参考Message Queue

6 扩展

6.1 Java世界中观察者模式

在前面的通用类图及其源代码实现中,通过观察你也许会发现,抽象的被观察者subject仅帮我们关联了观察者和确定了通知机制;抽象的观察者仅确立了被通知的方法。

这些完全可以单独抽象出一个联络类,作为观察者或被观察者的职责,这样就很非常符合单一职责原则。

幸运的是,Java从天始诞生就提供了一个可扩展的父类,即Java.util.Observable,这个类专用于让别人去触发,Java.util.Observer接口则专注于对观察者通知。

此外,还应关注Java.util.Observer接口丰富的方法,可以动态的添加/删除观察者。

6.2 项目中真实的观察者模式:

因为前面讲解的都是太标准的模式,在系统设计中会对观察者模式进行改造或改装,主要是下面三方面。

A。观察者与被观察者之间的消息沟通:被观察者状态改变时会触发观察者的一个行为,同时会传递一个消息给观察者,在实际中一般的做法是:观察者中的update方法接受两个参数,一个是被观察者,一个是DTOData Transfer Object,数据传输对象),DTO一般是一个纯JavaBean,由被观察者生成,由观察者消费。(若远程,则以XML格式传递)

B。观察者响应方式:观察者是个比较复杂的逻辑,要接受被观察者传递过来的信息,同时还要对他们进行逻辑处理,如果一个观察者对应多个被观察者,则需要考虑性能。备选方法两个:一是多线程,一是缓存技术。

C。被观察者尽量自己做主:不要把消息传到观察者时才判断是否需要消费。

应用举例:

文件系统:当在目录下新建一个文件,这个动作会同时通知目录管理器增加该目录,并通知磁盘管理器减少1KB的空间,也就是“文件”是一个被观察者,“目录管理器”则是观察者。

猫鼠游戏:猫叫一声,惊动了鼠;

广播收音机:电台在广播,收音机就能收听。

7 范例

7.1 标准的“观察者模式”可以参考通用源码

7.2 这里使用原书例子,使用java提供的观察者服务类。

类图如下:

需要注意的是,源码中有模拟处理耗时的情况,当然默认并没有多线程支持。。。

源代码如下:(作者原书例)

import java.util.Observable;/** * @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you *         all. 韩非子,李斯的师弟,韩国的重要人物 */public class HanFeiZi extends Observable {// 韩非子要吃饭了public void haveBreakfast() {System.out.println("韩非子:开始吃饭了...");// 通知所有的观察者super.setChanged();super.notifyObservers("韩非子在吃饭");}// 韩非子开始娱乐了,古代人没啥娱乐,你能想到的就那么多public void haveFun() {System.out.println("韩非子:开始娱乐了...");super.setChanged();this.notifyObservers("韩非子在娱乐");}}public class LiSi implements Observer {// 首先李斯是个观察者,一旦韩非子有活动,他就知道,他就要向老板汇报public void update(Observable observable, Object obj) {System.out.println("李斯:观察到李斯活动,开始向老板汇报了...");this.reportToQiShiHuang(obj.toString());System.out.println("李斯:汇报完毕,秦老板赏给他两个萝卜吃吃...\n");try {System.out.println("我开始休眠 " + System.currentTimeMillis());Thread.sleep(3000);System.out.println("我起来了 " + System.currentTimeMillis());} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}// 汇报给秦始皇private void reportToQiShiHuang(String reportContext) {System.out.println("李斯:报告,秦老板!韩非子有活动了--->" + reportContext);}}public class LiuSi implements Observer {// 刘斯,观察到韩非子活动后,自己也做一定得事情public void update(Observable observable, Object obj) {System.out.println("刘斯:观察到韩非子活动,开始动作了...");this.happy(obj.toString());System.out.println("刘斯:真被乐死了\n");}// 一看韩非子有变化,他就快乐private void happy(String context) {System.out.println("刘斯:因为" + context + ",——所以我快乐呀!");}}public class WangSi implements Observer {// 王斯,看到李斯有活动,自己就受不了public void update(Observable observable, Object obj) {System.out.println("Observable = " + observable.getClass()+ " , obj = " + obj);System.out.println("王斯:观察到韩非子活动,自己也开始活动了...");this.cry(obj.toString());System.out.println("王斯:真真的哭死了...\n");}// 一看李斯有活动,就哭,痛哭private void cry(String context) {System.out.println("王斯:因为" + context + ",——所以我悲伤呀!");}}public class Client {public static void main(String[] args) {// 三个观察者产生出来Observer liSi = new LiSi();Observer wangSi = new WangSi();Observer liuSi = new LiuSi();// 定义出韩非子HanFeiZi hanFeiZi = new HanFeiZi();// 我们后人根据历史,描述这个场景,有三个人在观察韩非子hanFeiZi.addObserver(wangSi);hanFeiZi.addObserver(liuSi);hanFeiZi.addObserver(liSi);// 然后这里我们看看韩非子在干什么hanFeiZi.haveBreakfast();}}

测试结果:

韩非子:开始吃饭了...

李斯:观察到李斯活动,开始向老板汇报了...

李斯:报告,秦老板!韩非子有活动了--->韩非子在吃饭

李斯:汇报完毕,秦老板赏给他两个萝卜吃吃...

我开始休眠 1337226592828

我起来了 1337226595828

刘斯:观察到韩非子活动,开始动作了...

刘斯:因为韩非子在吃饭,——所以我快乐呀!

刘斯:真被乐死了

Observable = class _16_Observer.HanFeiZi , obj = 韩非子在吃饭

王斯:观察到韩非子活动,自己也开始活动了...

王斯:因为韩非子在吃饭,——所以我悲伤呀!

王斯:真真的哭死了...