Observer Pattern

观察者模式定义

观察者模式又叫发布订阅模式(Publish/Subscribe),它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己。

我们可以使用日常生活中,期刊订阅的例子来形象地解释一下上面的概念。期刊订阅包含两个主要的角色:期刊出版方和订阅者,他们之间的关系如下:

  • 期刊出版方 - 负责期刊的出版和发行工作

  • 订阅者 - 只需执行订阅操作,新版的期刊发布后,就会主动收到通知,如果取消订阅,以后就不会再收到通知

在观察者模式中也有两个主要角色:Subject (主题) 和 Observer (观察者) 。它们分别对应例子中的期刊出版方和订阅者。接下来我们来看张图,从而加深对上面概念的理解。

观察者模式结构

观察者模式实战

Subject 类定义

class Subject {

    constructor() {
this.observerCollection = [];
} addObserver(observer) { // 添加观察者
this.observerCollection.push(observer);
} deleteObserver(observer) { // 移除观察者
let index = this.observerCollection.indexOf(observer);
if(index >= 0) this.observerCollection.splice(index, 1);
} notifyObservers() { // 通知观察者
this.observerCollection.forEach((observer)=>observer.notify());
}
}

Observer 类定义

class Observer {
constructor(name) {
this.name = name;
} notify() {
console.log(`${this.name} has been notified.`);
}
}

使用示例

let subject = new Subject(); // 创建主题对象

let observer1 = new Observer('semlinker'); // 创建观察者A - 'semlinker'
let observer2 = new Observer('lolo'); // 创建观察者B - 'lolo' subject.addObserver(observer1); // 注册观察者A
subject.addObserver(observer2); // 注册观察者B subject.notifyObservers(); // 通知观察者 subject.deleteObserver(observer1); // 移除观察者A subject.notifyObservers(); // 验证是否成功移除

以上代码成功运行后控制台的输出结果:

semlinker has been notified.
lolo has been notified.
lolo has been notified.

Observable subscribe

在介绍 RxJS - Subject 之前,我们先来看个示例:

const interval$ = Rx.Observable.interval(1000).take(3);

interval$.subscribe({
next: value => console.log('Observer A get value: ' + value);
}); setTimeout(() => {
interval$.subscribe({
next: value => console.log('Observer B get value: ' + value);
});
}, 1000);

以上代码运行后,控制台的输出结果:

Observer A get value: 0
Observer A get value: 1
Observer B get value: 0
Observer A get value: 2
Observer B get value: 1
Observer B get value: 2

通过以上示例,我们可以得出以下结论:

  • Observable 对象可以被重复订阅

  • Observable 对象每次被订阅后,都会重新执行

上面的示例,我们可以简单地认为两次调用普通的函数,具体参考以下代码:

function interval() {
setInterval(() => console.log('..'), 1000);
} interval(); setTimeout(() => {
interval();
}, 1000);

Observable 对象的默认行为,适用于大部分场景。但有些时候,我们会希望在第二次订阅的时候,不会从头开始接收 Observable 发出的值,而是从第一次订阅当前正在处理的值开始发送,我们把这种处理方式成为组播 (multicast),那我们要怎么实现呢 ?回想一下我们刚才介绍过观察者模式,你脑海中是不是已经想到方案了。没错,我们可以通过自定义 Subject 来实现上述功能。

自定义 Subject

Subject 类定义

class Subject {
constructor() {
this.observers = [];
} addObserver(observer) {
this.observers.push(observer);
} next(value) {
this.observers.forEach(o => o.next(value));
} error(error){
this.observers.forEach(o => o.error(error));
} complete() {
this.observers.forEach(o => o.complete());
}
}

使用示例

const interval$ = Rx.Observable.interval(1000).take(3);
let subject = new Subject(); let observerA = {
next: value => console.log('Observer A get value: ' + value),
error: error => console.log('Observer A error: ' + error),
complete: () => console.log('Observer A complete!')
}; var observerB = {
next: value => console.log('Observer B get value: ' + value),
error: error => console.log('Observer B error: ' + error),
complete: () => console.log('Observer B complete!')
}; subject.addObserver(observerA); // 添加观察者A
interval$.subscribe(subject); // 订阅interval$对象
setTimeout(() => {
subject.addObserver(observerB); // 添加观察者B
}, 1000);

以上代码运行后,控制台的输出结果:

Observer A get value: 0
Observer A get value: 1
Observer B get value: 1
Observer A get value: 2
Observer B get value: 2
Observer A complete!
Observer B complete!

通过自定义 Subject,我们实现了前面提到的功能。接下来我们进入正题 - RxJS Subject。

RxJS Subject

首先我们通过 RxJS Subject 来重写一下上面的示例:

const interval$ = Rx.Observable.interval(1000).take(3);
let subject = new Rx.Subject(); let observerA = {
next: value => console.log('Observer A get value: ' + value),
error: error => console.log('Observer A error: ' + error),
complete: () => console.log('Observer A complete!')
}; var observerB = {
next: value => console.log('Observer B get value: ' + value),
error: error => console.log('Observer B error: ' + error),
complete: () => console.log('Observer B complete!')
}; subject.subscribe(observerA); // 添加观察者A
interval$.subscribe(subject); // 订阅interval$对象
setTimeout(() => {
subject.subscribe(observerB); // 添加观察者B
}, 1000);

RxJS Subject 源码片段

/**
* Suject继承于Observable
*/
export class Subject extends Observable {
constructor() {
super();
this.observers = []; // 观察者列表
this.closed = false;
this.isStopped = false;
this.hasError = false;
this.thrownError = null;
} next(value) {
if (this.closed) {
throw new ObjectUnsubscribedError();
}
if (!this.isStopped) {
const { observers } = this;
const len = observers.length;
const copy = observers.slice();
for (let i = 0; i < len; i++) { // 循环调用观察者next方法,通知观察者
copy[i].next(value);
}
}
} error(err) {
if (this.closed) {
throw new ObjectUnsubscribedError();
}
this.hasError = true;
this.thrownError = err;
this.isStopped = true;
const { observers } = this;
const len = observers.length;
const copy = observers.slice();
for (let i = 0; i < len; i++) { // 循环调用观察者error方法
copy[i].error(err);
}
this.observers.length = 0;
} complete() {
if (this.closed) {
throw new ObjectUnsubscribedError();
}
this.isStopped = true;
const { observers } = this;
const len = observers.length;
const copy = observers.slice();
for (let i = 0; i < len; i++) { // 循环调用观察者complete方法
copy[i].complete();
}
this.observers.length = 0; // 清空内部观察者列表
}
}

通过 RxJS Subject 示例和源码片段,对于 Subject 我们可以得出以下结论:

  • Subject 既是 Observable 对象,又是 Observer 对象

  • 当有新消息时,Subject 会对内部的 observers 列表进行组播 (multicast)

Angular 2 RxJS Subject 应用

在 Angular 2 中,我们可以利用 RxJS Subject 来实现组件通信,具体示例如下:

message.service.ts

import { Injectable } from '@angular/core';
import {Observable} from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject'; @Injectable()
export class MessageService {
private subject = new Subject<any>(); sendMessage(message: string) {
this.subject.next({ text: message });
} clearMessage() {
this.subject.next();
} getMessage(): Observable<any> {
return this.subject.asObservable();
}
}

home.component.ts

import { Component } from '@angular/core';

import { MessageService } from '../_services/index';

@Component({
moduleId: module.id,
templateUrl: 'home.component.html'
}) export class HomeComponent {
constructor(private messageService: MessageService) {} sendMessage(): void { // 发送消息
this.messageService.sendMessage('Message from Home Component to App Component!');
} clearMessage(): void { // 清除消息
this.messageService.clearMessage();
}
}

app.component.ts

import { Component, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs/Subscription'; import { MessageService } from './_services/index'; @Component({
moduleId: module.id,
selector: 'app',
templateUrl: 'app.component.html'
}) export class AppComponent implements OnDestroy {
message: any;
subscription: Subscription; constructor(private messageService: MessageService) {
this.subscription = this.messageService.getMessage()
.subscribe(message => { this.message = message; });
} ngOnDestroy() {
this.subscription.unsubscribe();
}
}

以上示例实现的功能是组件之间消息通信,即 HomeComponent 子组件,向 AppComponent 父组件发送消息。代码运行后,浏览器的显示结果如下:

Plunker 示例

Subject 存在的问题

因为 Subject 在订阅时,是把 observer 存放到观察者列表中,并在接收到新值的时候,遍历观察者列表并调用观察者上的 next 方法,具体如下:

next(value) {
if (this.closed) {
throw new ObjectUnsubscribedError();
}
if (!this.isStopped) {
const { observers } = this;
const len = observers.length;
const copy = observers.slice();
for (let i = 0; i < len; i++) { // 循环调用观察者next方法,通知观察者
copy[i].next(value);
}
}
}

这样会有一个大问题,如果某个 observer 在执行时出现异常,却没进行异常处理,就会影响到其它的订阅者,具体示例如下:

const source = Rx.Observable.interval(1000);
const subject = new Rx.Subject(); const example = subject.map(x => {
if (x === 1) {
throw new Error('oops');
}
return x;
});
subject.subscribe(x => console.log('A', x));
example.subscribe(x => console.log('B', x));
subject.subscribe(x => console.log('C', x)); source.subscribe(subject);

以上代码运行后,控制台的输出结果:

A 0
B 0
C 0
A 1
Rx.min.js:74 Uncaught Error: oops

JSBin - Subject Problem Demo

在代码运行前,大家会认为观察者B 会在接收到 1 值时抛出异常,观察者 A 和 C 仍会正常运行。但实际上,在当前的 RxJS 版本中若观察者 B 报错,观察者 A 和 C 也会停止运行。那么应该如何解决这个问题呢?目前最简单的方式就是为所有的观察者添加异常处理,更新后的代码如下:

const source = Rx.Observable.interval(1000);
const subject = new Rx.Subject(); const example = subject.map(x => {
if (x === 1) {
throw new Error('oops');
}
return x;
}); subject.subscribe(
x => console.log('A', x),
error => console.log('A Error:' + error)
); example.subscribe(
x => console.log('B', x),
error => console.log('B Error:' + error)
); subject.subscribe(
x => console.log('C', x),
error => console.log('C Error:' + error)
); source.subscribe(subject);

JSBin - RxJS Subject Problem Solved Demo

RxJS Subject & Observable

Subject 其实是观察者模式的实现,所以当观察者订阅 Subject 对象时,Subject 对象会把订阅者添加到观察者列表中,每当有 subject 对象接收到新值时,它就会遍历观察者列表,依次调用观察者内部的 next() 方法,把值一一送出。

Subject 之所以具有 Observable 中的所有方法,是因为 Subject 类继承了 Observable 类,在 Subject 类中有五个重要的方法:

  • next - 每当 Subject 对象接收到新值的时候,next 方法会被调用

  • error - 运行中出现异常,error 方法会被调用

  • complete - Subject 订阅的 Observable 对象结束后,complete 方法会被调用

  • subscribe - 添加观察者

  • unsubscribe - 取消订阅 (设置终止标识符、清空观察者列表)

BehaviorSubject

BehaviorSubject 定义

BehaviorSubject 源码片段

export class BehaviorSubject extends Subject {
constructor(_value) { // 设置初始值
super();
this._value = _value;
}
get value() { // 获取当前值
return this.getValue();
}
_subscribe(subscriber) {
const subscription = super._subscribe(subscriber);
if (subscription && !subscription.closed) {
subscriber.next(this._value); // 为新的订阅者发送当前最新的值
}
return subscription;
}
getValue() {
if (this.hasError) {
throw this.thrownError;
}
else if (this.closed) {
throw new ObjectUnsubscribedError();
}
else {
return this._value;
}
}
next(value) { // 调用父类Subject的next方法,同时更新当前值
super.next(this._value = value);
}
}

BehaviorSubject 应用

有些时候我们会希望 Subject 能保存当前的最新状态,而不是单纯的进行事件发送,也就是说每当新增一个观察者的时候,我们希望 Subject 能够立即发出当前最新的值,而不是没有任何响应。具体我们先看一下示例:

var subject = new Rx.Subject();

var observerA = {
next: value => console.log('Observer A get value: ' + value),
error: error => console.log('Observer A error: ' + error),
complete: () => console.log('Observer A complete!')
}; var observerB = {
next: value => console.log('Observer B get value: ' + value),
error: error => console.log('Observer B error: ' + error),
complete: () => console.log('Observer B complete!')
}; subject.subscribe(observerA); subject.next(1);
subject.next(2);
subject.next(3); setTimeout(() => {
subject.subscribe(observerB); // 1秒后订阅
}, 1000);

以上代码运行后,控制台的输出结果:

Observer A get value: 1
Observer A get value: 2
Observer A get value: 3

通过输出结果,我们发现在 observerB 订阅 Subject 对象后,它再也没有收到任何值了。因为 Subject 对象没有再调用 next() 方法。但很多时候我们会希望 Subject 对象能够保存当前的状态,当新增订阅者的时候,自动把当前最新的值发送给订阅者。要实现这个功能,我们就需要使用 BehaviorSubject。

BehaviorSubject 跟 Subject 最大的不同就是 BehaviorSubject 是用来保存当前最新的值,而不是单纯的发送事件。BehaviorSubject 会记住最近一次发送的值,并把该值作为当前值保存在内部的属性中。接下来我们来使用 BehaviorSubject 重新一下上面的示例:

var subject = new Rx.BehaviorSubject(0); // 设定初始值

var observerA = {
next: value => console.log('Observer A get value: ' + value),
error: error => console.log('Observer A error: ' + error),
complete: () => console.log('Observer A complete!')
}; var observerB = {
next: value => console.log('Observer B get value: ' + value),
error: error => console.log('Observer B error: ' + error),
complete: () => console.log('Observer B complete!')
}; subject.subscribe(observerA); subject.next(1);
subject.next(2);
subject.next(3); setTimeout(() => {
subject.subscribe(observerB); // 1秒后订阅
}, 1000);

以上代码运行后,控制台的输出结果:

Observer A get value: 0
Observer A get value: 1
Observer A get value: 2
Observer A get value: 3
Observer B get value: 3

JSBin - BehaviorSubject

ReplaySubject

ReplaySubject 定义

ReplaySubject 源码片段

export class ReplaySubject extends Subject {
constructor(bufferSize = Number.POSITIVE_INFINITY,
windowTime = Number.POSITIVE_INFINITY,
scheduler) {
super();
this.scheduler = scheduler;
this._events = []; // ReplayEvent对象列表
this._bufferSize = bufferSize < 1 ? 1 : bufferSize; // 设置缓冲区大小
this._windowTime = windowTime < 1 ? 1 : windowTime;
} next(value) {
const now = this._getNow();
this._events.push(new ReplayEvent(now, value));
this._trimBufferThenGetEvents();
super.next(value);
} _subscribe(subscriber) {
const _events = this._trimBufferThenGetEvents(); // 过滤ReplayEvent对象列表
let subscription;
if (this.closed) {
throw new ObjectUnsubscribedError();
}
...
else {
this.observers.push(subscriber);
subscription = new SubjectSubscription(this, subscriber);
}
...
const len = _events.length;
// 重新发送设定的最后bufferSize个值
for (let i = 0; i < len && !subscriber.closed; i++) {
subscriber.next(_events[i].value);
}
...
return subscription;
}
} class ReplayEvent {
constructor(time, value) {
this.time = time;
this.value = value;
}
}

ReplaySubject 应用

有些时候我们希望在 Subject 新增订阅者后,能向新增的订阅者重新发送最后几个值,这时我们就可以使用 ReplaySubject ,具体示例如下:

var subject = new Rx.ReplaySubject(2); // 重新发送最后2个值

var observerA = {
next: value => console.log('Observer A get value: ' + value),
error: error => console.log('Observer A error: ' + error),
complete: () => console.log('Observer A complete!')
}; var observerB = {
next: value => console.log('Observer B get value: ' + value),
error: error => console.log('Observer B error: ' + error),
complete: () => console.log('Observer B complete!')
}; subject.subscribe(observerA); subject.next(1);
subject.next(2);
subject.next(3); setTimeout(() => {
subject.subscribe(observerB); // 1秒后订阅
}, 1000);

以上代码运行后,控制台的输出结果:

Observer A get value: 1
Observer A get value: 2
Observer A get value: 3
Observer B get value: 2
Observer B get value: 3

可能会有人认为 ReplaySubject(1) 是不是等同于 BehaviorSubject,其实它们是不一样的。在创建BehaviorSubject 对象时,是设置初始值,它用于表示 Subject 对象当前的状态,而 ReplaySubject 只是事件的重放。

JSBin - ReplaySubject

AsyncSubject

AsyncSubject 定义

AsyncSubject 源码片段

export class AsyncSubject extends Subject {
constructor() {
super(...arguments);
this.value = null;
this.hasNext = false;
this.hasCompleted = false; // 标识是否已完成
}
_subscribe(subscriber) {
if (this.hasError) {
subscriber.error(this.thrownError);
return Subscription.EMPTY;
}
else if (this.hasCompleted && this.hasNext) { // 等到完成后,才发出最后的值
subscriber.next(this.value);
subscriber.complete();
return Subscription.EMPTY;
}
return super._subscribe(subscriber);
}
next(value) {
if (!this.hasCompleted) { // 若未完成,保存当前的值
this.value = value;
this.hasNext = true;
}
}
}

AsyncSubject 应用

AsyncSubject 类似于 last 操作符,它会在 Subject 结束后发出最后一个值,具体示例如下:

var subject = new Rx.AsyncSubject();

  var observerA = {
next: value => console.log('Observer A get value: ' + value),
error: error => console.log('Observer A error: ' + error),
complete: () => console.log('Observer A complete!')
}; var observerB = {
next: value => console.log('Observer B get value: ' + value),
error: error => console.log('Observer B error: ' + error),
complete: () => console.log('Observer B complete!')
}; subject.subscribe(observerA); subject.next(1);
subject.next(2);
subject.next(3); subject.complete(); setTimeout(() => {
subject.subscribe(observerB); // 1秒后订阅
}, 1000);

以上代码运行后,控制台的输出结果:

Observer A get value: 3
Observer A complete!
Observer B get value: 3
Observer B complete!

JSBin - AsyncSubject

原文链接:https://segmentfault.com/a/1190000008886598

参考资源

RxJS - Subject(转)的更多相关文章

  1. import { Subject } from 'rxjs/Subject';

    shared-service.ts import { Observable } from 'rxjs/Observable'; import { Injectable } from '@angular ...

  2. [RxJS] Subject basic

    A Subject is a type that implements both Observer and Observable types. As an Observer, it can subsc ...

  3. [RxJS] Subject: an Observable and Observer hybrid

    This lesson teaches you how a Subject is simply a hybrid of Observable and Observer which can act as ...

  4. [RxJS] Subject asObservable() method

    You can create your own state store, not using any state management libraray. You might have seen th ...

  5. rxjs——subject和Observable的区别

    原创文章,转载请注明出处 理解 observable的每个订阅者之间,是独立的,完整的享受observable流动下来的数据的. subject的订阅者之间,是共享一个留下来的数据的 举例 这里的cl ...

  6. RxJS之Subject主题 ( Angular环境 )

    一 Subject主题 Subject是Observable的子类.- Subject是多播的,允许将值多播给多个观察者.普通的 Observable 是单播的. 在 Subject 的内部,subs ...

  7. [RxJS] Reusable multicasting with Subject factories

    The way we use publish() (or multicast with an RxJS Subject) makes the shared Observable not reusabl ...

  8. 【Rxjs】 - 解析四种主题Subject

    原文地址: https://segmentfault.com/a/1190000012669794 引言 开发ngx(angular 2+)应用时,基本上到处都会用到rxjs来处理异步请求,事件调用等 ...

  9. angular2 学习笔记 ( rxjs 流 )

    RxJS 博大精深,看了好几篇文章都没有明白. 范围牵扯到了函数响应式开发去了... 我对函数式一知半解, 响应式更是第一次听到... 唉...不过日子还是得过...混着过先呗 我目前所理解的很浅,  ...

随机推荐

  1. ligerUI利用a标签下载文件

    一.利用WriteFile实现下载,并验证文件是否存在,将指定的文件直接写入HTTP响应输出流.注意:大型文件使用此方法可能导致异常.可以使用此方法的文件大小取决于 Web 服务器的硬件配置. (1) ...

  2. 编程哲学之C#篇:01——创世纪

    我们能否像神一样地创建一个世界? 对于创建世界而言,程序员的创作能力最接近于神--相对于导演,作家,漫画家而言,他们创建的世界(作品)一旦完成,就再也不会变化,创建的角色再也不会成长.而程序员创建的世 ...

  3. 理解DDoS防护本质:基于资源较量和规则过滤的智能化系统

    本文由  网易云发布. 随着互联网生态逐渐形成,DDoS防护已经成为互联网企业的刚需要求,网易云安全(易盾)工程师根据DDoS的方方面面,全面总结DDoS的攻防对抗. 1.什么是DDoS DDoS全称 ...

  4. 程序媛计划——python数据库

    #实例:用数据库存储日记,实现日记本功能 #流程 #创建数据库 #coding:utf-8 import sqlite3 connect=sqlite3.connect('test.db') conn ...

  5. 怎样使用Navicat Premium导出导入mysql数据库

    首先,在Navicat Premium中连接要导出数据库的mysql数据库. 2 1.填写好连接数据库的信息后就可以连接到需要导出的数据库了. 3 打开要导出的数据库. 4 将数据库的结构和数据导出为 ...

  6. centos7无法上网问题

    在虚拟机VM中安装了centos7,无法上网,在同一个虚拟机里,也安装了Ubuntu是可以上网的,不知道咋回事,所以上网查了资料博客,现总结如下. 一.首先打开虚拟的设置,可以看到虚拟机网络的设置默认 ...

  7. js获取时间的函数集

    var mydate = new Date(); mydate.getYear(); //获取当前年份(2位) mydate.getFullYear(); //获取完整的年份(4位,1970-???? ...

  8. iOS自动布局框架-Masonry详解

    首先,在正式使用Masonry之前,我们先来看看在xib中我们是如何使用AutoLayout     从图中我们可以看出,只要设置相应得局限,控制好父视图与子视图之间的关系就应该很ok的拖出你需要的需 ...

  9. 服务器集群的session管理

    应用服务器的高可用架构设计主要基于服务无状态这一特性,但是事实上,业务总是有状态的,单机情况下,Session可由部署在服务器上的web容器(如Jboss)管理.在使用负载均衡的集群环境中,由于负载均 ...

  10. ASP.NET 下使用特定身份完成windows服务的功能操作

    今天部署项目的发现一个问题: 在本地Win7系统下利用Web页面完成Windows服务的功能操作(启动.停止.安装.卸载)都是正常的,而部署到Server2008系统下,再使用Web页面完成windo ...