观察者模式-将消息通知给观察者

公号:码农充电站pro
主页:https://codeshellme.github.io

观察者模式Observer Design Pattern)也被称为发布订阅模式Publish-Subscribe Design Pattern),主要用于更好的解决向对象通知消息的问题。

观察者模式定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。

观察者模式可以用很多称呼来描述,比如:

  • Subject-Observer:主题-观察者。
  • Publisher-Subscriber:发布者-订阅者。
  • Producer-Consumer:生产者-消费者。

1,订阅报纸

我们以订阅报纸为例,来描述 SubjectObserver 之间的关系。

Subject 相当于报社Observer 就相当于订阅报纸的用户

  • 从报社的角度来说:
    • 报社可向用户提供新闻消息,用户可以订阅报社的报纸,也可以取消订阅。
    • 报社记录了所有订阅报纸的用户名单
      • 如果有用户订阅了报纸,报社就会在名单中加入他的名字。
      • 如果有用户取消了报纸,报社就会从名单中删去他的名字。
    • 当报社有了新闻,会主动将新闻通知给它的所有订阅用户。
    • 没有订阅的用户,报社则不会去通知他。
  • 从用户的角度来说:
    • 如果用户订阅了报社的报纸,当报社有了新闻,他就会收到报社的消息。
    • 如果用户取消了报社的报纸,当报社有了新闻,报社也不会通知他。

SubjectObserver一对多关系

在这里插入图片描述

2,观察者模式类图

这里直接给出观察者模式的类图,这是最经典的实现方式,其它的变种都可以在它的基础上加以改进。

在这里插入图片描述

从类图中可以知道,Subject 是一个接口,有三个抽象方法:

  • registerObserver:用于注册 observer
  • removeObserver:用于移除 observer
  • notifyObservers:当有了消息,通知所有 observers

Observer 也是一个接口,有一个抽象方法:

  • update:当 subject 发来新消息时,用于更新消息。

3,实现观察者模式

下面我们来用代码实现观察者模式。

首先是两个接口 SubjectObserver

interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers(String info);
}

interface Observer {
    void update(String info);
}

这两个接口完全是按照类图中的内容来实现的,其中变量 info 的类型可以根据实际的应用场景来定。

再实现 ConcreteSubjectConcreteObserver

class ConcreteSubject implements Subject {

	// 用于存放 observer
    private final ArrayList<Observer> observers;

    public ConcreteSubject() {
        observers = new ArrayList();
    }

    public void registerObserver(Observer o) {
        observers.add(o);
    }

    public void removeObserver(Observer o) {
        observers.remove(o);
    }

    public void notifyObservers(String info) {
        for (Observer o: observers) {
            o.update(info);
        }
    }
}

class ConcreteObserver implements Observer {
    private final String name;

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

    public void update(String info) {
        System.out.println(this.name + " get info: " + info);
    }
}

ConcreteSubject 中的 observers 用于存储观察者,这里使用的类型是 ArrayList,也可以根据实际的应用场景来选择。

ConcreteObserver 中的 name 只是为了表示不同的观察者,观察者在收到消息后,将消息打印在控制台。

测试这两个类:

// 创建被观察者
ConcreteSubject s = new ConcreteSubject();

// 创建两个观察者
ConcreteObserver o1 = new ConcreteObserver("o1");
ConcreteObserver o2 = new ConcreteObserver("o2");

// 注册观察者
s.registerObserver(o1); // 注册 o1
s.registerObserver(o2); // 注册 o2
s.notifyObservers("info1");  // 向观察者通知消息

System.out.println("remove observer o1");

s.removeObserver(o1);  // 移除 o1
s.notifyObservers("info2"); // 再向观察者通知消息

输出如下:

o1 get info: info1
o2 get info: info1
remove observer o1
o2 get info: info2

可以看到,第一次通知消息时,o1o2 都收到消息了,在移除 o1 之后再发送消息,只有 o2 能收到消息。

这就是观察者模式最简洁的一种实现方式,非常简单。我把完整的代码放在了这里

4,观察者模式扩展

根据不同的应用场景和需求,观察者模式可以有不同的实现方式,比如下面几种:

  • 同步阻塞的实现方式
  • 异步非阻塞的实现方式
  • 进程内的实现方式
  • 跨进程的实现方式

同步阻塞方式

根据这种划分方式,上面我们实现的就是同步阻塞的方式,当有新消息的时候,Subject 会将消息 notify 给所有的 Observer,直到所有的 Observer 执行完毕它的 update 过程,整个通知过程才算完毕,这整个过程是一个阻塞的过程。

异步非阻塞方式

为了加快整个 notify 过程,我们可以将同步阻塞的方式改为异步非阻塞的方式。

一种简单的实现就是使用线程,就是在 update 方法中使用线程来完成任务,如下:

public void update(String info) {
   Thread t = new Thread(new Runnable() {
       public void run() {
           // 处理任务
       }
   });

   t.start();
}

Google Guava EventBusExplained 是一个通用的观察者模式框架,你可以去了解一下。

跨进程方式

同步阻塞异步非阻塞都属于进程之内的实现,对于跨进程的实现,一般都是基于消息队列来实现。至于这方面的应用,有很多现成的,成熟的组件可以使用,比如:

  • Redis Pub/Sub:一个快速、稳定的发布/订阅消息传递系统。
  • ActiveMQ:一个基于 Java 的多协议的消息传递服务。
  • RocketMQ:一个消息传递引擎。
  • Kafka:一个分布式的大数据流处理平台。

5,总结

观察者模式旨在将观察者与被观察者解耦,在很多地方都用到了该模式,比如 SwingJavaBeans 等。

观察者模式最经典的实现方式很简单,在实际应用中,可以在其基础上进行改进。

(本节完。)

推荐阅读:

设计模式之高质量代码

单例模式-让一个类只有一个对象

工厂模式-将对象的创建封装起来

策略模式-定义一个算法族

欢迎关注作者公众号,获取更多技术干货。

码农充电站pro

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结