iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 3
0
Modern Web

前端工程師一起來種一棵後端技能樹吧!系列 第 3

[Day 03] IOC 控制反轉 & DI 依賴注入 - (2)

上回說明了 IOC 與 DI 的概念與基本實作方式,並在文末提及每次都自行實作依賴注入當 codebase 一大成本也會相當高,因此能不能有一個統一的介面來實做這件事呢?今天就要來介紹 InversifyJS 這個用來實作 IOC 的第三方套件。

InversifyJS

首先先來看看 InversifyJS官網 是怎麼介紹自己的

A powerful and lightweight inversion of control container for JavaScript & Node.js apps powered by TypeScript.

它透過一個 'IOC container' 來統一管理類別的注入。
多說無益,直接用程式碼示範吧!

加入 InversifyJS 之前

先來架構出尚未使用 InversifyJS 的程式碼

專案架構

- root
    - main.ts
    - service.ts
    - dependencies.ts

main.ts 是建構出 service 實例的地方
service.ts 則是需要注入類別的 class
dependencies.ts 則存在兩個即將被 service 注入的類別

// dependencied.ts

export class DependencyA {
    private readonly name: string = 'dependencyA';

    public getName(): string {
        return this.name;
    }
}

export class DependencyB {
    private readonly name: string = 'dependencyB';

    public getName(): string {
        return this.name;
    }
}


// service.ts
import { DependencyA, DependencyB } from './dependencies';

export class KyleService {
    protected depA: DependencyA;
    protected depB: DependencyB;

    constructor() {
        this.depA = new DependencyA();
        this.depB = new DependencyB();
    }

    public getAllNames(): string[] {
        return [this.depA.getName(), this.depB.getName()];
    }
}

// main.ts
import { KyleService } from './service';

const service: KyleService = new KyleService();

console.log(service.getAllNames());

接著我們試著投入 InversifyJS 的懷抱吧!

// 安裝 InversifyJS 與 reflect-metadata(compile 用)

$ yarn add -D inversify reflect-metadata

接著在 tsconfig.json 中新增以下幾行,讓 TypeScript 能夠支援 InversifyJS

"compilerOptions": {
        ...,
        "types": ["reflect-metadata"],
        "moduleResolution": "node",
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
    },

以上都完成後就能在程式碼中使用 Inversify 啦

首先創建 di-container.ts 並在其中創建管理 DI 的 container

import { Container } from 'inversify';
import { DependencyA, DependencyB } from './dependencies';

let IOCContainer = new Container();

// instructs the container to return an instance of class whenever a particular class is requested (injected).
IOCContainer.bind<DependencyA>(DependencyA).toSelf();
IOCContainer.bind<DependencyB>(DependencyB).toSelf();

export default IOCContainer;

接著用 TS decorator 讓 dependency 標示為 injectable

// dependencies.ts
import { injectable } from 'inversify';

@injectable()
export class DependencyA {
    private readonly name: string = 'dependencyA';

    public getName(): string {
        return this.name;
    }
}

@injectable()
export class DependencyB {
    private readonly name: string = 'dependencyB';

    public getName(): string {
        return this.name;
    }
}

最後修改 service.ts 與 main.ts

// service.ts
import { inject, injectable } from 'inversify';
import { DependencyA, DependencyB } from './dependencies';

@injectable()
export class KyleService {
    protected depA: DependencyA;
    protected depB: DependencyB;

    constructor(
        @inject(DependencyA) dependencyA: DependencyA,
        @inject(DependencyA) dependencyB: DependencyB  
    ) {
        this.depA = dependencyA;
        this.depB = dependencyB;
    }

    public getAllNames(): string[] {
        return [this.depA.getName(), this.depB.getName()];
    }
}

// main.ts
import { KyleService } from './service';
import IOCContainer from './di-container';

const service: KyleService = IOCContainer.resolve<KyleService>(KyleService);

console.log(service.getAllNames());

如此一來 code 就比原本自己實作 IOC 時更為直覺,介面也更為統一了。

程式碼連結

github repo (其中的 ioc folder)

小結

藉由兩篇的篇幅介紹了 IOC 的概念,雖然不是一項 backend 限定的技術,然而卻是非常重要的撰寫優質程式碼的方法,尤其前後端開發有很大的機會會使用 OOP 的方式開發,如何撰寫易擴展、易維護的程式碼,朝更好的 OOP 走去,是這兩篇文章想分享給大家的。

團隊成員系列文

想盡辦法當好一個Junior Backend Developer
用舒服的姿勢開發 Python Project


上一篇
[Day 02] IOC 控制反轉 & DI 依賴注入 - (1)
下一篇
[Day 04] Message Queue - (1)
系列文
前端工程師一起來種一棵後端技能樹吧!30

尚未有邦友留言

立即登入留言