簡單實現類似Angular的依賴注入

依賴注入是一個很重要的設計模式。 它使用得非常廣泛,以至於幾乎每個人都把它簡稱爲 DI 。
Angular 有自己的依賴注入框架,離開它,你幾乎沒辦法構建出 Angular 應用。

下面介紹一種簡單的(只有100行左右代碼)實現類似Angular依賴注入的方式,先看一個例子

// a.service.ts
@Injectable()
export class AService {

  constructor() {

  }

  public doSomething() {
    console.log('this is AService::doSomething');
  }

}

// b.service.ts
@Injectable()
export class BService {

  constructor(private readonly a: AService) {

  }

  public doSomething() {
    this.a.doSomething();
    console.log('this is BService::doSomething');
  }

}

// some.module.ts
@Module({
  providers: [
    AService,
    BService,
  ],
})
export class SomeModule {

}

在上面的例子中,我們創建了兩個Service,其中BService依賴於AService,那麼BService可以在其構造函數中聲明其依賴,我們需要一種方法去自動將AService的實例注入到BService的私有隻讀變量a中,接下來介紹實現的步驟。

首先我們應該對Typescript進行配置,使其支持Javascript的裝飾器(或者說是註解),下面是我的配置文件:

{
  "compilerOptions": {
    "lib": [
      "dom",
      "es2015"
    ],
    "target": "es5",
    "module": "commonjs",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

其中,experimentalDecorators表示爲ES裝飾器啓用實驗支持emitDecoratorMetadata表示在源代碼中爲裝飾聲明產生類型的元數據,配置好了這兩項之後,我們才能使用Typescript的裝飾器,接下來我們需要安裝依賴reflect-metadata,用於讀取和設置元數據。

@Injectable的實現

@Injectable是一個裝飾器,它標識被裝飾的類是一個Provider,它的聲明方式如下

export function Injectable(): ClassDecorator {
  return (target) => {

  };
}

我們在此裝飾器中什麼都不做,他只起到一個標識的作用。

@Module的實現

@Module是一個裝飾器,它標識被裝飾的類是一個Module,它的聲明方式如下

const DI_IMPORTS_SYMBOL = Symbol('di:imports')
const DI_PROVIDERS_SYMBOL = Symbol('di:providers')

export function Module(options: { imports?: Array<any>, providers?: Array<any> }): ClassDecorator {
  return (target) => {
    Reflect.defineMetadata(DI_IMPORTS_SYMBOL, new Set(options.imports || []), target);
    Reflect.defineMetadata(DI_PROVIDERS_SYMBOL, new Set(options.providers || []), target);
  }
}

我們使用Set來存儲一個Module作用域中它所聲明的Providers和它所引入的其他模塊。

Factory的實現

我們希望達到的目的是,在使用時可以通過Factory.create(SomeModule)來獲取一個Module的實例,然後通過Module實例來獲取一個Provider,例如Factory.create(SomeModule).get(BService).doSomething(),此時應該輸出

Factory.create(SomeModule).get(BService).doSomething();
// this is AService::doSomething
// this is BService::doSomething

Talk is cheap. Show me the code:

export namespace Factory {

  export function create(module: Type) {
    const imports: Set<Type> = Reflect.getMetadata(DI_IMPORTS_SYMBOL, module);
    const providers: Set<any> = Reflect.getMetadata(DI_PROVIDERS_SYMBOL, module);
    const providersMap = new Map();

    const importModules = Array.from(imports).map((importModule) => {
      let moduleInstance: ModuleInstance = moduleInstances.get(importModule);
      if(!moduleInstance) {
        moduleInstance = create(importModule);
        moduleInstances.set(importModule, moduleInstance);
      }
      return moduleInstance;
    });
    const moduleInstance = new ModuleInstance(importModules, providersMap);

    providers.forEach(provider => {
      createProvider(provider, providers, moduleInstance);
    });
    return moduleInstance;
  }

  function createProvider(provider: any, providers: Set<any>, moduleInstance: ModuleInstance) {
    let providerInstance = moduleInstance.providers.get(provider);

    if(providerInstance) {
      return providerInstance;
    }

    const deps: Array<any> = Reflect.getMetadata('design:paramtypes', provider);
    if(!deps) {
      throw new Error(`No provider named ${ provider.name }, do yout add @Injectable() to this provider?`);
    }

    const args = deps.map(dep => {
      let depInstance = moduleInstance.providers.get(dep);
      if(!depInstance) {
        if(providers.has(dep)) {
          depInstance = createProvider(dep, providers, moduleInstance);
        } else {
          moduleInstance.imports.some(imp => {
            depInstance = createProvider(dep, new Set(), imp);
            return !!depInstance;
          });
        }
      }
      if(!depInstance) {
        throw new Error(`can not found provider ${ dep.name }`);
      }
      return depInstance;
    });
    providerInstance = new provider(...args);
    moduleInstance.providers.set(provider, providerInstance);
    return providerInstance;
  }

  export class ModuleInstance {

    constructor(
        public imports: Array<ModuleInstance>,
        public providers: Map<any, any>) {

    }

    get<T>(provider: Type<T>) {
      let instance: T = this.providers.get(provider);
      if(!instance) {
        this.imports.some(imp => {
          instance = imp.get(provider);
          return !!instance;
        });
      }
      if(!instance) {
        throw new Error(`No provider named: ${ provider.name }`);
      }
      return instance;
    }
  }

}

以上就是整個依賴注入的實現了,感興趣的朋友可以到我的Github上面查看源代碼,核心文件就是lib/di.ts,地址是
https://github.com/hungtcs/li...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章