Skip to content

什么是服务

服务是从英文单词Service翻译过来的。一个标准的服务对象应该是包含数据方法的对象。

特殊情况下也可能是没有方法的纯数据,或者没有数据只有方法的对象。

从来源角度看,在 js 中,可以直接通过对象字面量来定义一个对象,该对象就是服务。 也可以是实例化一个类得到一个对象,这个对象也是一个服务。 或者是通过一个工厂方法得到服务对象。

从使用场景看,容器,token,服务之间的关系如下:

ts
// 这里只是伪代码,单纯为了说明它们之间的关系。
container.bind(token, 服务);
const 服务对象 = container.get(token);

服务对象与服务工厂

注意区分文档中提到的服务,有时是指服务对象,有时是指服务工厂,需要自行区分。

toSelf

一个典型的例子就是类与实例化对象。实例化对象是一个服务对象,类则是服务工厂。

ts
class CountService {
  public count = 0;

  public addOne() {
    this.count++;
  }
}

const container = new Container();
container.bind(CountService).toSelf();
const countService = container.get(CountService);

上面例子中,CountService就是服务工厂,countService则是服务对象,这个服务对象有数据count属性,也有方法addOne

服务对象是由服务工厂生产的。在这里就是由类实例化而来。类的实例化过程是一个由抽象到具体的过程。服务的生产过程也是一个由抽象到具体的过程。

toConstantValue

服务也可以只有数据,比如现在有一个字符串变量代表网站的主题颜色,希望以服务的形式来表达。

ts
const theme = 'red';
const THEME_TOKEN = new Token<string>('theme');

const container = new Container();
container.bind(THEME_TOKEN).toConstantValue(theme);
const themeService = container.get(THEME_TOKEN);

注意到theme完全就是一个字符串而已,最终themeService也只是一摸一样的字符串。

那么为什么还需要通过container.get()来获取theme呢?

因为想要特意表达出一种抽象到具体的过程。theme只是服务的定义,themeService才是真正的服务对象。

和上面类的例子做对比来看:

  • theme相当于CountService
  • themeService相当于countService
  • toConstantValue相当于toSelf

只不过toSelf的作用是当用户使用container.get时,会自动实例化类,从而得到服务对象。 如果是toConstantValue,则是直接返回绑定的服务,就不存在实例化的过程。

toDynamicValue

通过工厂函数创建服务对象。

ts
function createCountService() {
  const count = 0;
  function increase() {
    this.count++;
  }
  return { count, increase };
}
type ICountService = ReturnType<typeof createCountService>;
const COUNT_SERVICE_TOKEN = new Token<ICountService>('count_service');

const container = new Container();
container.bind(COUNT_SERVICE_TOKEN).toDynamicValue(createCountService);
const countService = container.get(COUNT_SERVICE_TOKEN);

toService

可以给已经绑定的服务起一个别名。

ts
// 服务定义同上面的例子
const container = new Container();
container.bind(COUNT_SERVICE_TOKEN).toDynamicValue(createCountService);
const countService = container.get(COUNT_SERVICE_TOKEN);
// 创建一个新的token
const COUNT_SERVICE_TOKEN_V2 = new Token<ICountService>('count_service_v2');
// 再次绑定到createCountService
container.bind(COUNT_SERVICE_TOKEN_V2).toDynamicValue(createCountService);
const countServiceV2 = container.get(COUNT_SERVICE_TOKEN_V2);
// 注意到这里的两个服务对象是不相等的,也就是属于两个完全不同的对象
assert.isTrue(countService !== countServiceV2);

// 创建一个新的token
const COUNT_SERVICE_TOKEN_V3 = new Token<ICountService>('count_service_v3');
// 再次绑定到createCountService
container.bind(COUNT_SERVICE_TOKEN_V3).toService(COUNT_SERVICE_TOKEN);
const countServiceV3 = container.get(COUNT_SERVICE_TOKEN_V3);
// 这里是同一个服务对象
assert.isTrue(countService === countServiceV3);

思考

以上例子只是简单介绍了几种不同的绑定服务的方法,初看并不觉得这一套逻辑有什么优势,只是同一套代码换一种写法而已。

我理解这一套逻辑最大的优势是解耦了服务与服务之间的直接依赖关系。

如果是以前的写法,服务 A 依赖服务 B,那么就需要在服务 A 中直接 import 服务 B,然后使用服务 B。这是一种强依赖关系。

服务A --> 服务B

但是现在改为服务 A 只需要 import 服务 B 对应的 tokenB 即可,这是一种弱依赖关系。 体现的好处在于服务 A 虽然强依赖 tokenB,但是 tokenB 可以绑定到服务 B1,也可以随时更换成服务 B2,只需要保证它们具有相同的接口定义即可。

服务A --> tokenB --> 服务B1
服务A --> tokenB --> 服务B2

还有另一点优势是容器会自动帮助我们注入依赖,避免了手动收集依赖,手动初始化的流程。这在复杂业务场景中是非常便利的。

参考 Angular

Angular Provider

在 Angular 中类似的概念叫做 Provider,整体思想是想通的。

  • Angular 直接使用类相当于本库的 toSelf()
  • Angular 的 useClass 相当于本库的 to()
  • Angular 的 useValue 相当于本库的 toConstantValue
  • Angular 的 useExisting 相当于本库的 toService
  • Angular 的 useFactory 相当于本库的 toDynamicValue