天天看点

《AngularJS深度剖析与最佳实践》一2.9 服务

本节书摘来自华章出版社《angularjs深度剖析与最佳实践》一书中的第2章,第2.9节,作者 雪狼 破狼 彭洪伟,更多章节内容可以访问云栖社区“华章计算机”公众号查看

如果你是一个后端程序员,那么对服务(service)的概念一定不会陌生。在angular中,服务的概念是一样的,差别只在于技术细节。

服务是对公共代码的抽象,比如,如果在多个控制器中都出现了相似的代码,那么把它们提取出来,封装成一个服务,你将更加遵循dry原则(即:不要重复你自己),在可维护性等方面获得提升。

如同我们在第1章的tree服务中所看到的,由于服务剥离了和具体表现相关的部分,而聚焦于业务逻辑或交互逻辑,它更加容易被测试和复用。

但是,在工程实践中,我们引入服务的首要目的是为了优化代码结构,而不是复用。复用只是一项结果,不是目标。所以,当你发现你的代码中混杂了表现层逻辑和业务层逻辑的时候,你就要认真考虑抽取服务了—哪怕它还看不到复用价值。

如果你遵循着测试驱动开发的方式,那么当你觉得测试很难写的时候,回头审视下,看是否这里可以抽取出一个服务,转而对服务进行测试。

服务的概念通常是和依赖注入紧密相关的,angular中也一样。如果你困惑于在javascript中是如何实现依赖注入的,请参见第3章“背后的原理”中的3.3节“依赖注入”。

由于依赖注入的要求,服务都是单例的,这样我们才能把它们到处注入,而不用手动管理它们的生命周期,并容许angular实现“延迟初始化”等优化措施。

在angular中,服务分成很多种类型:

常量(constant):用于声明不会被修改的值。

变量(value):用于声明会被修改的值。

服务(service):没错,它跟服务这个大概念同名,原作者在“开发者指南”中把这种行为比喻为“把自己的孩子取名叫‘孩子’—一个会气疯老师的名字”。事实上,同名的原因是—它跟后端领域的“服务”实现方式最为相似:声明一个类,等待angular把它new出来,然后保存这个实例,供它到处注入。

工厂(factory):它跟上面这个“服务”不同,它不会被new出来,angular会调用这个函数,获得返回值,然后保存这个返回值,供它到处注入。它被取名为“工厂”是因为:它本身不会被用于注入,我们使用的是它的产品。但是与现实中的工厂不同,它只产出一份产品,我们只是到处使用这个产品而已。

供应商(provider):“工厂”只负责生产产品,它的规格是不受我们控制的,而“供应商”更加灵活,我们可以对规格进行配置,以便获得定制化的产品。

事实上,除了constant外,所有这些类型的服务,背后都是通过provider实现的,我们可以把它们看做让provider更容易写的语法糖。一个明显的佐证是:当你使用一个未定义的服务时,angular给你的错误提示是它对应的provider未找到,比如我们使用一个未定义的服务:test,那么angular给出的提示是:unknown provider: testprovider <- test。

constant比较特殊,我们稍后讲解,我们先来看其他几个。

provider的声明方式如下:

使用时:

对provider进行配置时:

容器的伪代码如下:

事实上,如果不需要对name参数进行配置,声明代码可以简化为:

angular.module('com.ngnice.app').value('greeting', 'hello, world');

这也就是需要这么多语法糖的原因。

下面给出其他语法糖的等价形式:

等价于:

在angular源码中,它们的实现是这样的:

angular提供了这么多种形式的服务,那么我们在工程实践中该如何选择?我们可以遵循下列决策流程:

需要全局的可配置参数?用provider。

是纯数据,没有行为?用value。

只new一次,不用参数?用service。

拿到类,我自己new出实例?用factory。

拿到函数,我自己调用?用factory。

但是,还有另一种更加敏捷的方式:

是纯数据时,先用value;当发现需要添加行为时,改写为service;或当发现需要通过计算给出结果时,改写为factory;当发现需要进行全局配置时,改写为provider。

最酷的是,这个过程对于使用者是透明的—它不需要因为实现代码的改动而更改原有代码。如上面value和factory的使用代码,仅仅从使用代码中我们区分不出它是value还是factory。

接下来,我们来看constant。与其他service不同,constant不是provider函数的语法糖。更重要的差别是,它的初始化时机非常早,可以在angular.module('com.ngnice.app').config函数中使用,而其他的服务是不能被注入到config函数中的。这也意味着,如果你需要在config中使用一个全局配置项,那么它就只能声明为常量,而不能声明为变量。

在官方的开发指南中,给出了一个完整的对比表,见表2-1。

《AngularJS深度剖析与最佳实践》一2.9 服务

下面给出解释:

可以依赖其他服务:由于value和constant的特殊声明形式,显然没有进行依赖注入的时机。

使用类型友好的注入:这条没有官方的解释,我的理解是—由于factory可以根据程序逻辑返回不同的数据类型,所以我们无法推断其结果是什么类型,也就是对类型不够友好。provider由于其灵活性比factory更高,因此在类型友好性上和factory是一样的。

在config阶段可用:只有constant和provider类型在config阶段可用,其他都是provider实例化之后的结果,所以只有config阶段完成后才可用。

可用于创建函数/原生对象:由于service是new出来的,所以其结果必然是类实例,也就无法直接返回一个可供调用的函数或数字等原生对象。

如果你确实需要对一个没有提供provider的第三方服务进行配置,该怎么办呢?angular提供了另一种机制:decorator。这个decorator和前面提到过的装饰器型指令没有关系,它是用来改变服务的行为的。

比如我们有一个第三方服务,名叫ui,它有一个prompt函数,我们不能改它源码,但需要让它每次弹出提问框时都在控制台输出一条记录,那么我们可以这样写:

这种方式给你了超级灵活性,你可以改写包括angular系统服务在内的任何服务—事实上,angular-mocks模块就是使用decorator来mock $httpbackend、$timeout等服务的。

不过,如果你大幅修改了原始服务的逻辑,那么,这可能会给自己和维护者挖坑。俗话说,“不作死就不会死”。如果让我来总结decorator的使用原则,那就是—慎用、慎用、慎用,如果确实想用,请务必遵循“liskov代换”原则,并写好单元测试。特别是,如果你想修改系统服务的工作逻辑,建议先多看几遍文档,确保你正确理解了它的每一个细节!

继续阅读