本文关键词: 观察者模式 。
观察者模式是什么?
观察者模式和回调函数有什么关系?
使用httpClient时,加上.subscribe有什么作用?
Angular的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;
依然是选出一个,格式化成熟悉的形式:
可以看出,这个方法需要传入三个回调函数,分别为:
所以只要把对应的方法传入,就可以了(也可以不都传入,subscribe由于有重载函数,可以处理不同的参数)。
在调用.subscribe时,为什么要传入success和error两个方法?
——其实是为了代码解耦,回调函数的目的本来就是为了代码解耦。在方法A()中传入方法B()用于回调,就可以把方法A()执行之后的数据交给方法B()来操作。所以方法A()执行后的数据是不变的,具体怎么操作这个数据,就要看传进去的方法了。
同理,观察者向数据源发起订阅之后,当数据源发生变化时,把新的数据通知给订阅者。数据既然已经拿到手,怎么处理数据就是 订阅者 的事了,和数据源没关系了。所以在观察者模式里使用回调函数的好处在于:当处理返回值的功能发生变化时,并不用改动 数据源 的任何代码。
这就好比:杂志社把杂志交给客户之后,客户想不想看、什么时候看,或者想把杂志扔掉,都是客户自己的事情,和杂志社没有半毛钱的关系。
数据源 + 订阅者 = 观察者模式
观察者模式,是设计模式里面最简单的,也是最好理解的一种。
笔者也处于初学阶段,以后会学到更多的设计模式。对于学习的过程来说,最大的喜悦,无非就是那种豁然开朗的感觉了,从一开始的一团浆糊到后来的融会贯通。这种喜悦,应该就是学习最大的回报吧。