设计模式中的观察者模式

观察者模式是一种软件设计模式,其中一个名为主体(Subject)的对象维护其依赖项列表,称为观察者,并通常通过调用它们(observers)的方法之一来自动通知它们任何状态更改。

观察者模式主要用于在“事件驱动”软件中实现分布式事件处理系统。在这些系统中,主体 Subject 通常被称为“事件流(stream of events)”或“事件流源”,而观察者被称为“事件接收器”。

流命名法暗示了一种物理设置,其中观察者在物理上是分开的,并且无法控制从主题/流源发出的事件。

这种模式非常适合任何进程,其中数据从启动时 CPU 不可用的某些输入到达,而是“随机”到达(HTTP 请求、GPIO 数据、来自键盘/鼠标/的用户输入...、分布式数据库)和区块链,...)。

大多数现代编程语言都包含实现观察者模式组件的内置“事件”结构。虽然不是强制性的,但大多数“观察者”实现将使用后台线程监听主题事件和内核提供的其他支持机制(Linux epoll,...)。

观察者设计模式是二十三个著名的“四人帮”设计模式之一,描述了如何解决反复出现的设计挑战,以设计灵活且可重用的面向对象软件,即更容易实现、更改、 测试和重用。

What problems can the Observer design pattern solve?

观察者模式解决了以下问题:

  • 对象之间的一对多依赖关系应该在不使对象紧密耦合的情况下定义。

  • 应该确保当一个对象改变状态时,自动更新无限数量的依赖对象。

  • 一个对象可以通知无限数量的其他对象应该是可能的。

通过定义一个直接更新依赖对象状态的对象(主体)来定义对象之间的一对多依赖是不灵活的,因为它将主体耦合到特定的依赖对象。尽管如此,从性能的角度来看,或者如果对象实现是紧密耦合的(想想每秒执行数千次的低级内核结构),它仍然有意义。在某些情况下,紧密耦合的对象可能难以实现,并且难以重用,因为它们引用并了解(以及如何更新)具有不同接口的许多不同对象。在其他情况下,紧密耦合的对象可能是更好的选择,因为编译器将能够在编译时检测错误并在 CPU 指令级别优化代码。

What solution does the Observer design pattern describe?

定义主题和观察者对象。

这样当一个主题改变状态时,所有注册的观察者都会被自动通知和更新(可能是异步的)。

主体的唯一职责是维护观察者列表并通过调用它们的 update() 操作通知它们状态变化。 观察者的职责是在一个主题上注册(和取消注册)自己(以获得状态变化的通知)并在收到通知时更新他们的状态(将他们的状态与主题的状态同步)。 这使得主体和观察者松散耦合。 主体和观察者彼此之间没有明确的感知。 可以在运行时独立添加和删除观察者。 这种通知-注册交互也称为发布-订阅。

Strong vs. weak reference

观察者模式会导致内存泄漏,称为失效侦听器问题,因为在基本实现中,它需要显式注册和显式取消注册,就像在处置模式中一样,因为主体持有对观察者的强引用,使它们保持活动状态。 这可以通过主体持有对观察者的弱引用来防止。

Coupling and typical pub-sub implementations

通常,观察者模式被实现,因此被“观察”的“主体”是正在观察状态变化的对象的一部分(并传达给观察者)。这种类型的实现被认为是“紧密耦合的”,迫使观察者和主体相互了解并可以访问它们的内部部分,从而产生可扩展性、速度、消息恢复和维护(也称为事件或通知)的可能问题损失),条件分散缺乏灵活性,以及可能妨碍所需的安全措施。在发布-订阅模式(又名发布-订阅模式)的一些(非轮询)实现中,这是通过创建一个专用的“消息队列”服务器(有时还有一个额外的“消息处理程序”对象)作为额外阶段来解决的观察者和被观察对象之间,从而解耦组件。在这些情况下,消息队列服务器由观察者使用观察者模式访问,“订阅某些消息”只知道预期的消息(或在某些情况下不知道),而对消息发送者本身一无所知;发送者也可能对观察者一无所知。发布订阅模式的其他实现,实现了类似的通知和向感兴趣的各方通信的效果,根本不使用观察者模式。

在 OS/2 和 Windows 等多窗口操作系统的早期实现中,术语“发布-订阅模式”和“事件驱动的软件开发”被用作观察者模式的同义词。

正如 GoF 书中所描述的,观察者模式是一个非常基本的概念,并没有解决在通知观察者之前或之后消除对观察到的“主体”或被观察“主体”所做的特殊逻辑的更改的兴趣。该模式也不处理发送更改通知时的记录或保证收到更改通知。这些问题通常在消息队列系统中处理,其中观察者模式只是其中的一小部分。

观察者模式的 UML 和 时序图

在上面的UML类图中,Subject类并没有直接更新依赖对象的状态。 相反,Subject 引用 Observer 接口(update())来更新状态,这使得 Subject 独立于依赖对象的状态如何更新。 Observer1 和 Observer2 类通过将它们的状态与主题的状态同步来实现 Observer 接口。 UML 序列图显示了运行时交互:Observer1 和Observer2 对象调用Subject1 上的attach(this) 来注册它们自己。 假设 Subject1 的状态发生了变化,Subject1 会对其自身调用 notify() 。 notify() 对已注册的 Observer1 和 Observer2 对象调用 update(),它们从 Subject1 请求更改的数据 (getState()) 以更新(同步)它们的状态。

UML 类图

看一个 Java 的例子。

Subject 即数据源的实现:

import java.util.List;import java.util.ArrayList;import java.util.Scanner;class EventSource {
    public interface Observer {
        void update(String event);
    }

    private final List<Observer> observers = new ArrayList<>();

    private void notifyObservers(String event) {
        observers.forEach(observer -> observer.update(event));
    }

    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    public void scanSystemIn() {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()) {
            String line = scanner.nextLine();
            notifyObservers(line);
        }
    }}

Observer 的实现:

public class ObserverDemo {
    public static void main(String[] args) {
        System.out.println("Enter Text: ");
        EventSource eventSource = new EventSource();

        eventSource.addObserver(event -> {
            System.out.println("Received response: " + event);
        });

        eventSource.scanSystemIn();
    }}

JavaScript 的实现:

let Subject = {
    _state: 0,
    _observers: [],
    add: function(observer) {
        this._observers.push(observer);
    },
    getState: function() {
        return this._state;
    },
    setState: function(value) {
        this._state = value;
        for (let i = 0; i < this._observers.length; i++)
        {
            this._observers[i].signal(this);
        }
    }};let Observer = {
    signal: function(subject) {
        let currentValue = subject.getState();
        console.log(currentValue);
    }}Subject.add(Observer);Subject.setState(10);//Output in console.log - 10

更多Jerry的原创文章,尽在:"汪子熙":

观察者模式是一种软件设计模式,其中一个名为主体(Subject)的对象维护其依赖项列表,称为观察者,并通常通过调用它们(observers)的方法之一来自动通知它们任何状态更改。

观察者模式主要用于在“事件驱动”软件中实现分布式事件处理系统。在这些系统中,主体 Subject 通常被称为“事件流(stream of events)”或“事件流源”,而观察者被称为“事件接收器”。

流命名法暗示了一种物理设置,其中观察者在物理上是分开的,并且无法控制从主题/流源发出的事件。

这种模式非常适合任何进程,其中数据从启动时 CPU 不可用的某些输入到达,而是“随机”到达(HTTP 请求、GPIO 数据、来自键盘/鼠标/的用户输入...、分布式数据库)和区块链,...)。

大多数现代编程语言都包含实现观察者模式组件的内置“事件”结构。虽然不是强制性的,但大多数“观察者”实现将使用后台线程监听主题事件和内核提供的其他支持机制(Linux epoll,...)。

观察者设计模式是二十三个著名的“四人帮”设计模式之一,描述了如何解决反复出现的设计挑战,以设计灵活且可重用的面向对象软件,即更容易实现、更改、 测试和重用。

What problems can the Observer design pattern solve?

观察者模式解决了以下问题:

  • 对象之间的一对多依赖关系应该在不使对象紧密耦合的情况下定义。

  • 应该确保当一个对象改变状态时,自动更新无限数量的依赖对象。

  • 一个对象可以通知无限数量的其他对象应该是可能的。

通过定义一个直接更新依赖对象状态的对象(主体)来定义对象之间的一对多依赖是不灵活的,因为它将主体耦合到特定的依赖对象。尽管如此,从性能的角度来看,或者如果对象实现是紧密耦合的(想想每秒执行数千次的低级内核结构),它仍然有意义。在某些情况下,紧密耦合的对象可能难以实现,并且难以重用,因为它们引用并了解(以及如何更新)具有不同接口的许多不同对象。在其他情况下,紧密耦合的对象可能是更好的选择,因为编译器将能够在编译时检测错误并在 CPU 指令级别优化代码。

What solution does the Observer design pattern describe?

定义主题和观察者对象。

这样当一个主题改变状态时,所有注册的观察者都会被自动通知和更新(可能是异步的)。

主体的唯一职责是维护观察者列表并通过调用它们的 update() 操作通知它们状态变化。 观察者的职责是在一个主题上注册(和取消注册)自己(以获得状态变化的通知)并在收到通知时更新他们的状态(将他们的状态与主题的状态同步)。 这使得主体和观察者松散耦合。 主体和观察者彼此之间没有明确的感知。 可以在运行时独立添加和删除观察者。 这种通知-注册交互也称为发布-订阅。

Strong vs. weak reference

观察者模式会导致内存泄漏,称为失效侦听器问题,因为在基本实现中,它需要显式注册和显式取消注册,就像在处置模式中一样,因为主体持有对观察者的强引用,使它们保持活动状态。 这可以通过主体持有对观察者的弱引用来防止。

Coupling and typical pub-sub implementations

通常,观察者模式被实现,因此被“观察”的“主体”是正在观察状态变化的对象的一部分(并传达给观察者)。这种类型的实现被认为是“紧密耦合的”,迫使观察者和主体相互了解并可以访问它们的内部部分,从而产生可扩展性、速度、消息恢复和维护(也称为事件或通知)的可能问题损失),条件分散缺乏灵活性,以及可能妨碍所需的安全措施。在发布-订阅模式(又名发布-订阅模式)的一些(非轮询)实现中,这是通过创建一个专用的“消息队列”服务器(有时还有一个额外的“消息处理程序”对象)作为额外阶段来解决的观察者和被观察对象之间,从而解耦组件。在这些情况下,消息队列服务器由观察者使用观察者模式访问,“订阅某些消息”只知道预期的消息(或在某些情况下不知道),而对消息发送者本身一无所知;发送者也可能对观察者一无所知。发布订阅模式的其他实现,实现了类似的通知和向感兴趣的各方通信的效果,根本不使用观察者模式。

在 OS/2 和 Windows 等多窗口操作系统的早期实现中,术语“发布-订阅模式”和“事件驱动的软件开发”被用作观察者模式的同义词。

正如 GoF 书中所描述的,观察者模式是一个非常基本的概念,并没有解决在通知观察者之前或之后消除对观察到的“主体”或被观察“主体”所做的特殊逻辑的更改的兴趣。该模式也不处理发送更改通知时的记录或保证收到更改通知。这些问题通常在消息队列系统中处理,其中观察者模式只是其中的一小部分。

观察者模式的 UML 和 时序图

在上面的UML类图中,Subject类并没有直接更新依赖对象的状态。 相反,Subject 引用 Observer 接口(update())来更新状态,这使得 Subject 独立于依赖对象的状态如何更新。 Observer1 和 Observer2 类通过将它们的状态与主题的状态同步来实现 Observer 接口。 UML 序列图显示了运行时交互:Observer1 和Observer2 对象调用Subject1 上的attach(this) 来注册它们自己。 假设 Subject1 的状态发生了变化,Subject1 会对其自身调用 notify() 。 notify() 对已注册的 Observer1 和 Observer2 对象调用 update(),它们从 Subject1 请求更改的数据 (getState()) 以更新(同步)它们的状态。

UML 类图

看一个 Java 的例子。

Subject 即数据源的实现:

import java.util.List;import java.util.ArrayList;import java.util.Scanner;class EventSource {
    public interface Observer {
        void update(String event);
    }

    private final List<Observer> observers = new ArrayList<>();

    private void notifyObservers(String event) {
        observers.forEach(observer -> observer.update(event));
    }

    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    public void scanSystemIn() {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()) {
            String line = scanner.nextLine();
            notifyObservers(line);
        }
    }}

Observer 的实现:

public class ObserverDemo {
    public static void main(String[] args) {
        System.out.println("Enter Text: ");
        EventSource eventSource = new EventSource();

        eventSource.addObserver(event -> {
            System.out.println("Received response: " + event);
        });

        eventSource.scanSystemIn();
    }}

JavaScript 的实现:

let Subject = {
    _state: 0,
    _observers: [],
    add: function(observer) {
        this._observers.push(observer);
    },
    getState: function() {
        return this._state;
    },
    setState: function(value) {
        this._state = value;
        for (let i = 0; i < this._observers.length; i++)
        {
            this._observers[i].signal(this);
        }
    }};let Observer = {
    signal: function(subject) {
        let currentValue = subject.getState();
        console.log(currentValue);
    }}Subject.add(Observer);Subject.setState(10);//Output in console.log - 10
(0)

相关推荐

  • 通俗易懂系列 | 设计模式(七):观察者模式

    介绍# 观察者模式是行为设计模式之一.当您对对象的状态感兴趣并希望在有任何更改时收到通知时,观察者设计模式非常有用.在观察者模式中,监视另一个对象状态的对象称为Observer,正在被监视的对象称为S ...

  • PHP设计模式之观察者模式

    PHP设计模式之观察者模式 观察者,貌似在很多科幻作品中都会有这个角色的出现.比如我很喜欢的一部美剧<危机边缘>,在这个剧集中,观察者不停的穿越时空记录着各种各样的人或事.但是,设计模式中 ...

  • 行为型模式之观察者模式

    在现实世界中,许多对象并不是独立存在的,其中一个对象的行为发生改变可能会导致一个或者多个其他对象的行为也发生改变.例如,某种商品的物价上涨时会导致部分商家高兴,而消费者伤心. 在软件世界也是这样,例如 ...

  • 设计模式(20) 观察者模式

    观察者模式是一种平时接触较多的模式.它主要用于一对多的通知发布机制,当一个对象发生改变时自动通知其他对象,其他对象便做出相应的反应,同时保证了被观察对象与观察对象之间没有直接的依赖. GOF对观察者模 ...

  • 设计模式之观察者模式

    设计模式之观察者模式

  • 设计模式中的那些工厂

    Intro 设计模式中有几个工厂模式,聊一聊这几个工厂模式的各自用法和使用示例,工厂模式包含简单工厂,抽象工厂,工厂方法,这些均属于创建型模式, 所谓创建型模式,就是说这几个设计模式是用来创建对象的. ...

  • 设计模式-行为型-观察者模式

    观察者模式(Observer): 指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新. 观察者模式的角色: 1)抽象目标(Subject):也叫抽象 ...

  • Java中的设计模式(一):观察者模式

    工具与资源中心 帮助开发者更加高效的工作,提供围绕开发者全生命周期的工具与资源https://developer.aliyun.com/tool?spm=a1z389.11499242.0.0.654 ...

  • Thread中run和start方法的模板设计模式

    创建一个Thread需要继承Thread重写run方法或者实现Runnable接口中的run方法,其实两者都是一样因为Thread也继承了Runnable接口. 实现了run方法,但是启动确实用sta ...

  • [PHP小课堂]PHP设计模式之观察者模式

    [PHP小课堂]PHP设计模式之观察者模式 关注公众号:[硬核项目经理]获取最新文章 添加微信/QQ好友:[DarkMatterZyCoder/149844827]免费得PHP.项目管理学习资料

  • 嵌入式开发中为什么很少用设计模式?

    刚开始工作的两年,我做项目写代码不会考虑代码扩展.移植.模块化等,导致项目有新增功能.或修改的时候不知如何下手.今天分享一篇肖遥整理的关于设计模式的文章,希望对大家有帮助. 工作有些年了,每每看到一些 ...

  • 嵌入式开发中需要用到设计模式吗?

    工作有些年了,每每看到一些朋友会问,设计模式需要学吗?好像做嵌入式的从没遇到过需要用设计模式的,所以一直没系统学习,但是我也知道这个很重要,久而久之,到头来还是没学. 这里我说一下自己的看法和思考,来 ...

  • .net core 中的经典设计模式的应用

    .net core 中的经典设计模式的应用 Intro 前段时间我们介绍了23种设计模式,今天来分享一下 .net core 源码中我觉得比较典型的设计模式的应用 实例 责任链模式 asp.net c ...