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. 01-Python的基础知识1

    基础即常识. - Python的对象模型 - Python中一切皆对象. - 内置对象:数字,字符串,列表,元组,集合等等. - 基本输入 - 基本模式    变量 = input("提示字 ...

  2. MVC4删除 pages引发的异常 System.Web.Optimization找不到引用

    在MVC4的开发中,如果创建的项目为空MVC项目,那么在App_Start目录下没有BundleConfig.cs项的内容,在手动添加时在整个库中都找不到:System.Web.Optimizatio ...

  3. DOM操作复选框

    DOM操作复选框 实现一个效果如下图: 点击"全选/全不选"复选框之后,足球.篮球.游泳和唱歌均被选取.取消"全选/全不选"复选框之后,被选中的所有都变成未选中 ...

  4. celery初始化

    # 在任务处理者一端加初始化 import os import django os.environ.setdefault("DJANGO_SETTINGS_MODULE", &qu ...

  5. WebDriverAPI(1)

    访问某网页地址 被测网址http:http://www.baidu.com Java语言版本的API实例代码 方法一: @Test public void visitURL(){ String bas ...

  6. 移动端h5直播项目|html5直播实战开发|h5仿陌陌

    最近一些直播.小视频什么的都比较火,像陌陌.抖音.火山短视频… 于是空闲时间自己也利用html5技术也试着倒腾了下直播项目,使用到了h5+css3+iscroll+zepot+swiper+wlsPo ...

  7. 搜集C++实现的线程池

    现在很多语言都内置了线程池实现,但C++中却没有.本文列举一些C++实现的线程池工具. Boost.Threadpool 项目首页:http://threadpool.sourceforge.net ...

  8. sql 函数字符串处理

    --Description: 字符处理 --使用: 放到查询分析器里执行就可以了 --示例: select * from dbo.splitstr('12 44 45 50 56 87',' ') o ...

  9. chroot的用法

    chroot命令用来在指定的根目录下运行指令.chroot,即 change root directory (更改 root 目录).在 linux 系统中,系统默认的目录结构都是以/,即是以根 (r ...

  10. LDAP落地实战(二):SVN集成OpenLDAP认证

    上一篇文章我们介绍了LDAP的部署以及管理维护,那么如何接入LDAP实现账号统一认证呢?这篇文章将带你完成svn的接入验证 subversion集成OpenLDAP认证 系统环境:debian8.4 ...