理解观察者模式——用Angular的httpClient来解释观察者

javascript/jquery

浏览数:10

2020-5-19

AD:资源代下载服务

前言

本文关键词:观察者模式

观察者模式是什么?
观察者模式和回调函数有什么关系?
使用httpClient时,加上.subscribe有什么作用?
Angular的httpClient中如何体现观察者模式?

解决了上述问题之后,就写了这篇文章。

(这篇文章实际上是给上一篇文章填个坑…上一篇写到回调函数,却没有给出实际应用的例子。)

引入问题:httpClient

httpClient是Angular中的一个内置类,用于向后台发起Http请求、返回请求结果。用它来举例子是因为功能比较简单,易于理解。

在Angular中有这么一种写法:

// 向8080端口的helloWorld路径发起请求
httpClient.get('http://localhost:8080/helloWorld')
  .subscribe((data) => {
    console.log('请求成功');
    console.log(data);
  }, error);
}

不经意一看,这不就是简单的链式调用么?——前一个方法返回一个对象,再调用这个对象的方法,再返回对象,再调用方法…
但是仔细看,才发现:这根本就不是一个过程,而是两个过程啊!

从使用.get向后台发起请求之后,到调用.subscribe之前,这之中经历了一个后台接收数据、处理数据、返回数据的过程。

那么问题来了:挖掘机技术哪家强?前台如何知道数据什么时候返回?怎么在数据返回之后,自动执行后面的代码来打印返回数据?

观察者模式

观察者模式,顾名思义,有这样一个对象,在始终被另一个对象观察着、注视着、紧紧的盯着。
用杂志社做比喻:有一个杂志社杂志社里有一个订阅报刊的部门,一个读者向这个部门订阅了杂志,从此以后读者日日期盼着读到自己买的杂志,而订阅部门也会在新的杂志出版之后,第一时间送到读者手里。
这就是观察者模式,它由两部分组成:
数据源 + 订阅者 = 观察者模式

数据源和订阅者之间是一对多的关系。订阅者通过某种方法,向数据源发起订阅,此后,数据源一旦发生变更,会马上通知所有的订阅者。

既然知道了原理,那么在httpClient中具体是怎么实现的呢?
我们找到源码,httpClient类的所有方法都写在里面,并且有一大堆重载:

我们拿出一个方法来看看:

    /**
     * Constructs a `GET` request that interprets the body as a text string
     * and returns the response as a string value.
     *
     * @param url     The endpoint URL.
     * @param options The HTTP options to send with the request.
     *
     * @return An `Observable` of the response, with the response body of type string.
     */
    get(url: string, options: {
        headers?: HttpHeaders | {
            [header: string]: string | string[];
        };
        observe?: 'body';
        params?: HttpParams | {
            [param: string]: string | string[];
        };
        reportProgress?: boolean;
        responseType: 'text';
        withCredentials?: boolean;
    }): Observable<string>;

把代码格式化一下,变成我们容易理解的方式:

可以看到get方法的必填参数是请求地址URL,还有可选参数。但这些都不重要,关键在于,普通的方法,用方法名+参数就完事了,比如:

test();

再看这个方法的最后,多出了一个: Observable<string>这是观察者的关键!这条代码的意思是:返回类型为“观察者”的对象,这个观察者携带着string类型的被观察的数据。

Observable是“可观察的”意思,声明一个方法有可观察的对象之后,这个方法的返回值就不再是一个普通变量,而是一个“观察者”对象,我们对这个对象使用.subscribe方法订阅,就可以传入函数进行回调了。
我们在控制台打印一下.get()方法的返回值:

console.log(this.httpClient.get(`http://localhost:8080/Klass/${klass.id}`));


果然是一个对象,这个对象包含了订阅数据源的功能,等到数据返回之后再使用.subscribe方法来操作返回的数据。

接下来,我们来看subscribe方法:

subscribe(observer?: PartialObserver<T>): Subscription;
/** @deprecated Use an observer instead of a complete callback */
subscribe(next: null | undefined, error: null | undefined, complete: () => void): Subscription;
/** @deprecated Use an observer instead of an error callback */
subscribe(next: null | undefined, error: (error: any) => void, complete?: () => void): Subscription;
/** @deprecated Use an observer instead of a complete callback */
subscribe(next: (value: T) => void, error: null | undefined, complete: () => void): Subscription;
subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): Subscription;

依然是选出一个,格式化成熟悉的形式:

可以看出,这个方法需要传入三个回调函数,分别为:

  • HTTP请求执行成功后执行什么方法
  • HTTP请求失败后执行什么方法
  • 无论成功或失败,最后执行什么方法

所以只要把对应的方法传入,就可以了(也可以不都传入,subscribe由于有重载函数,可以处理不同的参数)。

观察者和回调函数有什么关系?

在调用.subscribe时,为什么要传入success和error两个方法?

——其实是为了代码解耦,回调函数的目的本来就是为了代码解耦。在方法A()中传入方法B()用于回调,就可以把方法A()执行之后的数据交给方法B()来操作。所以方法A()执行后的数据是不变的,具体怎么操作这个数据,就要看传进去的方法了。

同理,观察者向数据源发起订阅之后,当数据源发生变化时,把新的数据通知给订阅者。数据既然已经拿到手,怎么处理数据就是订阅者的事了,和数据源没关系了。所以在观察者模式里使用回调函数的好处在于:当处理返回值的功能发生变化时,并不用改动数据源的任何代码。
这就好比:杂志社把杂志交给客户之后,客户想不想看、什么时候看,或者想把杂志扔掉,都是客户自己的事情,和杂志社没有半毛钱的关系。

总结

数据源 + 订阅者 = 观察者模式
观察者模式,是设计模式里面最简单的,也是最好理解的一种。

笔者也处于初学阶段,以后会学到更多的设计模式。对于学习的过程来说,最大的喜悦,无非就是那种豁然开朗的感觉了,从一开始的一团浆糊到后来的融会贯通。这种喜悦,应该就是学习最大的回报吧。

作者:LYX6666