0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

TypeScriptAdvent Calendar 2021

Day 3

【TypeScript】非情報系卒の駆け出しエンジニアなりにDIするコードをvanillaで書いてみた

Last updated at Posted at 2021-12-02

前書き

日常を過ごしていて、「TypeScriptでDIできたらいいな~。できればvanillaで。」なんて思うことはありませんか?
私はふと道を歩いているときや湯船に浸かっているときになります。
なんて思いながら調べてみるとInversifyJStsyringeに辿りつきますが、vanillaじゃないですよね。
なので今回は足りない脳みそを濡れた雑巾のように搾り取り、強引に依存性を注入するコードを書きました。
ですので、この文章は搾りカスの出涸らしが書いています。いつもより稚拙な文章ですがお付き合いください。

追記(2022/05/15): 続きを書きました。 Re:【TypeScript】非情報系卒の駆け出しエンジニアなりにDIするコードをvanillaで書いてみた

要件

  • パッケージのインストールを用いない
  • 使う側になるべく依存性の注入を意識させない

コード

対象インスタンスを継承元が同じクラスの異なるインスタンスに渡し、対象インスタンスのプロパティを変化させることで依存性の注入が出来ているかを確認します。
なお、以下に示すコードファイルはすべて同階層に置いています。

target.ts

対象インスタンスとなるクラスです。
変化させる対象のプロパティは param で、インクリメントとデクリメントができるようにしてあります。

target.ts
export class Target {
    private param: number;
    constructor() {
        this.param = 0;
    }
    increment() {
        this.param++;
    }
    decrement() {
        this.param--;
    }
    get currentParam() {
        return this.param;
    }
}

cls.ts

依存性注入するクラスです。
ファイル名が安直でウケますね。
継承先にDIを意識させたくなかったため、デコレータ+implementsを用いる方向で試行錯誤したのですが、力不足で実現できませんでした。
なので継承元にコンストラクタとは別に init() を作成し、それを呼び出すことで継承先がDIを意識しにくくしつつ依存性注入を行っています。そうなるとメソッド名は _init() にしたほうがよさそうですね。
また、 init() が呼び出される前提のため、対象インスタンスを格納する target!: Target; はその場しのぎをしちゃっています。危ないですね。
init() は下に記載する呼び出すタイミングでコードを簡略化するために this を返却しています。
ここではAとBの2つのクラスを定義しています。これらを呼び出します。

cls.ts
import { Target } from './target';

export abstract class Base {
    abstract member: string;
    target!: Target;
    constructor() {}
    init(target: Target): this {
        this.target = target;
        return this;
    }
    increment(): void {
        this.target.increment();
    }
    decrement(): void {
        this.target.decrement();
    }
}

export class A extends Base {
    member = 'A';
    constructor() {
        super();
    }
}

export class B extends Base {
    member = 'B';
    constructor() {
        super();
    }
}

main.ts

実行するファイルです。
変数名が雑で申し訳ないです。短いコードなので許してください。
DIできているかを確認するために main() でtargetインスタンスを作成しています。
setting() で注入を行いつつインスタンスを作成して返します。
また、init() したい関係でBaseから継承されているクラスのみを依存性注入対象としたいため <T extends Base> しています。
注入対象クラスはインスタンスを受け取る形にしたくないため、型

interface Type<T> extends Function {
    new (...args: any[]): T;
}

を定義しています。これは

>>> import { A } from './cls';
>>> typeof A
Function

より、Function型を継承していると捉えています。
"捉えています"というのは、このコードはAngular type.tsからまんまパクっているためです。

main.ts
import { Base, A, B } from './cls';
import { Target } from './target';

interface Type<T> extends Function {
    new (...args: any[]): T;
}

function setting<T extends Base>(target: Target, classes: Type<T>[]): T[] {
    return classes.map(t => (new t()).init(target));
}

function main() {
    const target = new Target();
    const d = setting(target, [A, B]);
    d.forEach(c => c.increment());
    console.log(target.currentParam);  // 2
}

main();

AとBそれぞれで1回ずつ increment() しているため2が出力されます。

おわりに

問題点だらけなので参考程度に留めてください。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?