设计模式之——观察者模式

1 观察者模式的定义

观察者模式:Observer Pattern,也叫发布-订阅模式(Publish-Subscribe Pattern)。具体定义是:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。
观察者模式的通用类图:

观察者模式通用类图

  • Subject被观察者
    定义被观察者必须实现的职责,它必须能够动态地增加、取消观察者。一般是抽象类或者接口,仅仅完成作为被观察者必须实现的职责:管理观察者并通知观察者。
  • ConcreteSubject具体的被观察者
    定义被观察者自己的业务逻辑,同时定义对哪些事件进行通知。
  • Observer观察者
    观察者接收到消息后,就进行update操作,对接收到的信息进行处理。
  • ConcreteObserver具体的观察者
    每个观察者在接收到消息后的处理应该是不同的,每个观察者有自己独特的处理逻辑。

2 观察者模式通用代码示例

  1. 被观察者
  • 抽象的被观察者
@Slf4j
public abstract class Subject {
    /**
     * 定义一个观察者数组
     */
    private Vector<Observer> observers = new Vector<>();

    /**
     * 增加一个观察者
     *
     * @return true:增加成功;false:增加失败
     */
    public boolean addObserver(Observer observer) {
        return this.observers.add(observer);
    }

    /**
     * 删除一个观察者
     *
     * @return true:删除成功;false:删除失败
     */
    public boolean removeObserver(Observer observer) {
        return this.observers.remove(observer);
    }

    /**
     * 通知所有的观察者
     */
    public void notifyAllObservers() {
        log.info("{}通知所有的观察者.", this.getClass().getSimpleName());
        for (Observer observer : observers) {
            observer.update();
        }
    }
}
  • 具体被观察者
@Slf4j
public class ConcreteSubject extends Subject {

    /**
     * 具体被观察者独特的业务逻辑
     */
    public void uniqueMethod() {
        //具体业务
        log.info("具体业务逻辑{}.uniqueMethod", this.getClass().getSimpleName());
        super.notifyAllObservers();
    }
}
  1. 观察者
  • 观察者接口
    观察者一般是一个接口。
public interface Observer {
    /**
     * 观察者的操作
     */
    public void update();
}
  • 具体观察者
    每一个实现该接口的实现类都是具体观察者。
@Slf4j
public class ConcreteObserver implements Observer {
    /**
     * 实现具体的观察者接口,具有独特的业务
     */
    @Override
    public void update() {
        log.info("观察者{}接收到消息,正在进行处理...", this.getClass().getSimpleName());
    }
}
  1. 场景类
@Slf4j
public class Client {
    public static void main(String[] args) {
        //创建一个被观察者
        ConcreteSubject subject = new ConcreteSubject();
        //定义一个观察者
        Observer observer = new ConcreteObserver();
        //观察者观察被观察者
        subject.addObserver(observer);
        //被观察者进行活动,观察者进行观察者
        subject.uniqueMethod();
    }
}

3 观察者模式的优缺点

3.1 优点

  1. 观察者和被观察者之间是抽象耦合
    观察者和被观察者之间通过抽象耦合,这样设计在增加观察者或被观察者的时候,都非常容易扩展,不修改原有代码只需要增加具体实现就可以进行扩展,符合“开闭原则”。
  2. 建立一套触发机制
    观察者模式可以更方便地实现具体业务中的复杂逻辑关系,形成逻辑链条。

3.2 缺点

  1. 开发效率低
    一个被观察者、多个观察者,开发和调试都会比较复杂,在大型项目中影响开发效率。
  2. 执行效率低
    在Java中消息的通知默认是顺序执行,一个观察者出错,会影响整体的执行效率,因此一般可以考虑采用异步方式。尤其是在多级触发的时候,执行效率更会受影响。
    因此在设计时注意考虑影响。

4 观察者模式的应用

4.1 应用场景

  • 关联行为场景
    关联行为是可拆分的,而不是“组合”关系。
  • 事件多级触发场景
  • 跨系统的消息交换场景
    如消息队列的处理机制。

4.2 使用观察者模式的注意事项

使用观察者模式有两个重点问题需要解决。

  1. 广播链问题
    观察者模式中的观察者同时也可以是被观察者,因此很容易形成逻辑链。逻辑链一旦建立,逻辑就比较复杂,可维护性非常差。
    根据经验建议:
    在一个观察者模式中,最多出现一个对象既是观察者也是被观察者,也就是说消息最多转发一次(传递两次),还是比较好控制的。

广播链责任链模式最大的区别是:

  • 观察者广播链:消息在传播的过程中是随时更改的,它是由相邻的两个节点协商的消息结构。
  • 责任链模式:消息在传递的过程中基本上保持不变。如果要改变,也只有在原有的消息上进行修正。
  1. 异步处理问题
    如果被观察者发生动作了,观察者要做出响应。如果观察者比较多,就要用异步处理。异步处理就需要考虑线程安全和队列的问题,详情可以参考JDK的Message Queue。

5 观察者模式的扩展

5.1 Java中的观察者模式

Java本身支持了观察者模式,并且提供了可用于扩展的父类:java.util.Observable实现类和java.util.Observer接口。

Java中观察者模式通用类图

  1. 被观察者
    被观察者,继承java.util.Observable:
@Slf4j
public class ConcreteSubject extends Observable {
    /**
     * 具体被观察者独特的业务逻辑
     */
    public void uniqueMethod() {
        //具体业务
        log.info("具体业务逻辑{}.uniqueMethod", this.getClass().getSimpleName());
    }

    /**
     * 业务逻辑
     */
    public void doSomething() {
        log.info("被观察者{}执行业务逻辑...", this.getClass().getSimpleName());
        this.setChanged();
        this.notifyObservers();
    }
}
  1. 观察者
    观察者实现java.util.Observer接口:
@Slf4j
public class ConcreteObserver implements Observer {
    /**
     * 观察者名字
     */
    private String name;

    public ConcreteObserver(String name) {
        this.name = name;
    }

    @Override
    public void update(Observable o, Object arg) {
        log.info("观察者[{}]观察到变化。{}.update", this.name, this.getClass().getSimpleName());
        ConcreteSubject observer= (ConcreteSubject) o;
        observer.uniqueMethod();
    }
}
  1. 场景类
    场景应用观察者和被观察者
@Slf4j
public class Client {
    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();
        ConcreteObserver observer1 = new ConcreteObserver("OBSERVER1");
        ConcreteObserver observer2 = new ConcreteObserver("OBSERVER2");
        subject.addObserver(observer1);
        subject.addObserver(observer2);

        subject.doSomething();
    }
}

运行结果:

23:04:10.055 [main] INFO com.idear.design.pattern.observer.jdk.ConcreteSubject - 被观察者ConcreteSubject执行业务逻辑...
23:04:10.055 [main] INFO com.idear.design.pattern.observer.jdk.ConcreteObserver - 观察者[OBSERVER2]观察到变化。ConcreteObserver.update
23:04:10.055 [main] INFO com.idear.design.pattern.observer.jdk.ConcreteSubject - 具体业务逻辑ConcreteSubject.uniqueMethod
23:04:10.055 [main] INFO com.idear.design.pattern.observer.jdk.ConcreteObserver - 观察者[OBSERVER1]观察到变化。ConcreteObserver.update
23:04:10.055 [main] INFO com.idear.design.pattern.observer.jdk.ConcreteSubject - 具体业务逻辑ConcreteSubject.uniqueMethod

5.2 订阅-发布模型

EJB3(Enterprise JavaBean)是一个非常优秀的框架,比起前两代,还算轻量级。EJB3中有3个类型的Bean:

  • Session Bean
  • Entity Bean
  • MessageDriven Bean。
    MessageDriven Bean(MDB),消息驱动Bean:消息的发布者(Provider)发布一个消息(一个消息驱动Bean),通过EJB容器(一般是Message Queue消息队列)通知订阅者做出相应。原理上很简单,类似于观察者模式的升级版。

6 项目中真实的观察者模式

一般在真实的项目中,系统设计时会对观察者模式进行改造或改装,主要体现在以下3个方面。

  1. 观察者和被观察者之间的消息沟通
    被观察者状态改变会触发观察者的一个行为,同时传递一个消息给观察者。在实际中一般做法是:观察者中的update方法接受两个参数,一个是被观察者,一个是DTO(Data Transfer Object,数据传输对象)。DTO一般是一个纯粹的JavaBean,由被观察者生成,由观察者消费。如果考虑到远程传输,可以通过JSON或XML格式传递。
  2. 观察者响应方式
    观察者一般逻辑复杂,需要接受被观察者传递过来的信息并进行处理。当一个观察者——多个被观察者时,性能就需要进行考虑。如果观察者来不及响应,被观察者执行时间也会被拉长。这种情况有两种处理办法:
  • 异步架构
    采用多线程技术,被观察者或者观察者都可以启动线程,可以明显地提高系统性能。
  • 同步架构
    采用缓存技术,保证有足够的资源,随时响应。但是,代价是开发难度大,需要进行充分的压力测试。
  1. 被观察者尽量自己做主
    被观察者具体实现根据业务逻辑决定是否要通知观察者,而不是消息到达观察者时才判断是否需要消费,导致浪费网络资源,加重消费者的处理逻辑和处理压力。

参考

  1. 设计模式之禅
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容

  • 有人说婚姻是爱情的坟墓,有人说婚姻是爱情最好的归宿,也有人婚前没有爱情依然携手走了一生。不得不说,好的婚姻真的是可...
    月灵零阅读 137评论 0 0
  • 转眼间光阴飞逝,儿子已满3岁。作为独生子女一代,我常被妈妈唠叨以自我为中心、不懂得付出、急躁沉不住气等毛病。以前我...
    唐都阅读 1,036评论 0 50