第一个DI:第一个用于打字稿应用程序接口的DI

我共享一个名为First DI的库。多年来,它一直帮助我解决了诸如React,Preact,Mithril等库的浏览器应用程序中的依赖注入问题。在编写First DI时,以C#和Java语言的DI库(例如autofac,java spring autowired,ninject等)的思想为基础。就像这些语言中的库一样,First DI基于反射和Typescript接口工作。

DI有什么用?


简而言之,Dependecy Injection(以下称为DI)是构建Clean Architecture的主要部分之一。这样,您就可以用该部分程序的另一种实现替换该程序的任何部分。DI是用于实现和替换程序部分的工具。

特别是,它允许您:

  • 为每个平台分别替换逻辑实现。例如,在浏览器中,通过Notifications API显示通知,并且在组装移动应用程序时,通过移动平台的API替换为通知。
  • 替换各种环境中的逻辑实现。例如,可以在产品电路中使用开发者的本地机器无法访问的支付网关或通知分发系统。因此,代替开发阶段的实际付款或邮寄,可以用存根替换逻辑。
  • 替换数据源。例如,代替对服务器的真实请求,剥离预先准备的数据以测试业务逻辑或布局以符合布局。
  • 还有更多有用的用途,但现在还不行……

首先,准备工作工具。

使用准备


要使用它,您只需要做3件简单的事情:

  1. 连接反射元数据 polyfill
  2. 在tsconfig文件中,启用embedDecoratorMetadata和experimentalDecorators选项。第一个允许您生成反射,第二个包括对装饰器的支持
  3. 创建任何空装饰器,例如const Reflection =(..._ params:Object []):void => {}或使用库中现成的装饰器

现在,我将以最简单的形式演示其用法。

使用简单


例如,以使用Pure Architecture编写的程序为例:

import { autowired, override, reflection } from "first-di";

@reflection // typescript  
class ProdRepository { //   

    public async getData(): Promise<string> {
        return await Promise.resolve("production");
    }

}

@reflection
class MockRepository { //       

    public async getData(): Promise<string> {
        return await Promise.resolve("mock");
    }

}

@reflection
class ProdService {

    constructor(private readonly prodRepository: ProdRepository) { }

    public async getData(): Promise<string> {
        return await this.prodRepository.getData();
    }

}

class ProdController { //   React, Preact, Mithril  .

    @autowired() //  
    private readonly prodService!: ProdService;

    // constructor  ,   React, Preact, Mithril
    //      

    public async getData(): Promise<string> {
        return await this.prodService.getData();
    }

}

if (process.env.NODE_ENV === "test") { //    
    override(ProdRepository, MockRepository);
}

const controllerInstance = new ProdController(); //   
const data = await controllerInstance.getData();

if (process.env.NODE_ENV === "test") {
    assert.strictEqual(data, "mock"); //  
} else {
    assert.strictEqual(data, "production"); //  
}

在Pure Architecture中,您只需要添加两行代码,自动连接和覆盖即可使DI开始工作。现在,该程序已经具有用于生产的实现和用于测试的实现。

由于First DI主要是为客户端应用程序编写的,因此默认情况下,依赖项将实现为Singleton。为了全局更改此行为,有一些默认选项,以及自动装配的装饰器的参数和override()方法。

您可以进行如下更改:

//  1 
import { DI, AutowiredLifetimes } from "first-di";
DI.defaultOptions.lifeTime = AutowiredLifetimes.PER_INSTANCE;

//  2  
import { autowired, AutowiredLifetimes } from "first-di";
@autowired({lifeTime: AutowiredLifetimes.PER_INSTANCE})
private readonly prodService!: ProdService;

//  3  
import { override, AutowiredLifetimes } from "first-di";
override(ProdRepository, MockRepository, {lifeTime: AutowiredLifetimes.PER_INSTANCE});

这是使用DI的最简单方法。但是在企业中,使用接口代替特定的实现,这是将要实现的实现的契约。为此有第二种操作模式。

专业使用


对于专业用途,我们用接口替换特定的实现。但是,打字稿不会为运行时中运行的界面生成代码。幸运的是,有一个简单的解决方案,为了理解,有必要回顾一下理论……什么是接口?这是一个完全抽象的类!

幸运的是,Typescript支持完全抽象的类,生成运行时代码,并允许使用抽象的类来描述类型。

我们将使用此信息并编写一个企业程序:

import { autowired, override, reflection } from "first-di";

abstract class AbstractRepository { //    

    abstract getData(): Promise<string>;

}

@reflection
class ProdRepository implements AbstractRepository {

    public async getData(): Promise<string> {
        return await Promise.resolve("production");
    }

}

@reflection
class MockRepository implements AbstractRepository {

    public async getData(): Promise<string> {
        return await Promise.resolve("mock");
    }

}

abstract class AbstractService { //    

    abstract getData(): Promise<string>;

}

@reflection
class ProdService implements AbstractService {

    private readonly prodRepository: AbstractRepository;

    constructor(prodRepository: AbstractRepository) {
        this.prodRepository = prodRepository;
    }

    public async getData(): Promise<string> {
        return await this.prodRepository.getData();
    }

}

class ProdController { //   React, Preact, Mithril  .

    @autowired()
    private readonly prodService!: AbstractService;

    // constructor  ,   React, Preact, Mithril
    //      

    public async getData(): Promise<string> {
        return await this.prodService.getData();
    }

}

override(AbstractService, ProdService);

if (process.env.NODE_ENV === "test") {
    override(AbstractRepository, MockRepository);
} else {
    override(AbstractRepository, ProdRepository);
}

const controllerInstance = new ProdController();
const data = await controllerInstance.getData();

if (process.env.NODE_ENV === "test") {
    assert.strictEqual(data, "mock");
} else {
    assert.strictEqual(data, "production");
}

现在,我们有一个现成的专业程序,该程序没有默认实现,并且所有合同都已在应用程序组装阶段被替换为实现。

将DI嵌入到您的应用程序中就这么简单。使用C#和Java编写的人员将能够使用Web开发中的现有经验。

其它功能


使用DI的多个副本:

import { DI } from "first-di";
import { ProductionService } from "../services/ProductionService";

const scopeA = new DI();
const scopeB = new DI();

export class Controller {

    @scopeA.autowired()
    private readonly serviceScopeA!: ProductionService;

    @scopeB.autowired()
    private readonly serviceScopeB!: ProductionService;

    // constructor  ,   React, Preact, Mithril
    //      

    public async getDataScopeA(): Promise<string> {
        return await this.serviceScopeA.getData();
    }

    public async getDataScopeB(): Promise<string> {
        return await this.serviceScopeB.getData();
    }

}

可扩展性,您可以编写自己的DI:

import { DI } from "first-di";

class MyDI extends DI {
    // extended method
    public getAllSingletons(): IterableIterator<object> {
        return this.singletonsList.values();
    }
}

GitHub上存储库中描述了更多有趣的功能

为什么自己写而不使用?


原因很简单-编写此DI时没有其他选择。这是角度1不再相关且角度2不会退出的时候。类刚刚出现在Javascript中,而反射出现在typescript中。顺便提及,反射的出现已成为行动的主要动力。

InversifyJS-即使当前形式也不适合我。样板太多。另外,通过行或字符进行注册会消除重构依赖项的可能性。

喜欢吗


如果您喜欢这种DI帮助,则会使其变得更受欢迎。尝试一下,发送请求,放星星。GitHub的仓库

All Articles