LoginSignup
2

More than 3 years have passed since last update.

posted at

updated at

DIPについて勉強し直してみた

こちらは、 CAMエンジニア Advent Calendar 2019 9日目の記事です。
昨日は @nonoakij さんの
Flexboxで「width:0」にしてうまくいく場合について研究してみたでした。

今回は僕が勉強して、業務に生かされていることについて書こうと思います!

概要

最近、もっといいコードを書くためにどうしたらいいかと勉強していた中でSOLID原則に出会いました
その中でD(dip)について勉強したので、それについて備忘録的なとこも含め書きたいと思います

dipとは

The Dependency Inversion Principleのことで訳すと、依存性逆転の原則のことです

上位のモジュールは下位のモジュールに依存してはならない。
どちらのモジュールも「抽象」に依存すべきである 「抽象」は実装の詳細に依存してはならない。
実装の詳細が「抽象」に依存すべきである。

では

「抽象」に依存すべきである

を具体的に実装するためにはどうしたらいいのか

diしましょう

diって?

  • Dependency Injectionのこと
  • デザインパターンの一つ
  • 日本語訳だと「依存性の注入」
  • あるオブジェクトを別のオブジェクトに渡すデザインパターンがDIパターン

マイクロソフトのランゲージポータルには、「dependency」について下記のような説明がある

「機能Aが機能Bに依存している場合、BがAのdependencyである」

AがBに依存している関係はあまりよろしくない。なので 抽象に依存しよう って話に繋がります

そして、「依存性の注入」って言われるより
「依存オブジェクトの注入」って言われると、わかりやすいですが実装ベースでの話です

特徴

オブジェクトの生成と使用が分離されている
AがBを呼ぶのではなく、Bが外部からAに注入されることにより、制御を反転させることができる

メリット

具体的なことを書かないので記述量が減る
オブジェクトを再利用できる
保守性、柔軟性が高くなる

デメリット

具体的なことを書いてないので、コードだけみても何が起きてるか追いづらい

コード

diに従わないパターンと従うパターンを書いてみました

従わないパターン


interface ServiceInterface {
  doSomeThing(): void;
}

class Client {
  private service: ServiceInterface;
  constructor () {
    this.service = new Service();
  }

  doSomeThing () {
    this.service.doSomeThing();
  }
}

class Service {
  doSomeThing(): void {}
}

従ったパターン

interface ServiceInterface {
  doSomeThing(): void;
}

class Client {
  private service: ServiceInterface;
  constructor (service: ServiceInterface) {
    this.service = service;
  }

  doSomeThing () {
    this.service.doSomeThing();
  }
}

class Service implements ServiceInterface {
  doSomeThing(): void {}
}

しかし

DIに従えばとりあえず良いってわけではなくて引数が増えちゃうし、準備が大変です


interface ServiceInterface {
  doSomeThing(): void;
}

interface ProductInterface {
  useSomething(): void;
}

class Client {
  private service: ServiceInterface;
  private product: ProductInterface;

  constructor (service: ServiceInterface, product: ProductInterface) {
    this.service = service;
    this.product = product;
  }

  doSomeThing () {
    this.service.doSomeThing();
  }

  useSomething () {
    this.product.useSomething();
  }
}

class Service implements ServiceInterface {
  doSomeThing(): void {}
}

class Product implements ProductInterface {
  useSomething (): void {};
}

// うーーーん
new Client(
  new Service(),
  new Product(),
);

DIコンテナを使おう

今は引数が二つだけなので、まだ読めるけどこれがどんどん増えたらつらく大変なのでDIコンテナを使いましょう!
DIコンテナとはDI機能を提供するフレームワークです

  • マイクロソフト製のDIコンテナ → tsyringe
  • よくみかけるDIコンテナ → InversifyJS

僕は両方使ってみましたが、tsyringeの方が簡単に書けるなーって思いましたが
InversifyJSの方が軽量だったので、InversifyJSを使ってみました

InversifyJSで書いたら、大体こんな感じになりました

import {
 injectable,
 inject
} from 'inversify';

import {
  ServiceInterface,
  ProductInterface,
  ClientInterface,
} from '../interface';

import { diTypes } from '../types';

@injectable()
export class Client implements ClientInterface {
  private service: ServiceInterface;
  private product: ProductInterface;

  constructor (
    @inject(diTypes.service) service: ServiceInterface,
    @inject(diTypes.product) product: ProductInterface,
  ) {
    this.service = service;
    this.product = product;
  }

  doSomeThing () {
    this.service.doSomeThing();
  }

  useSomething () {
    this.product.useSomething();
  }
}
inversify.config.ts
import { Container } from 'inversify';
import { diTypes } from './types';
import {
  ServiceInterface,
  ProductInterface,
  ClientInterface,
} from '../interface';
import {
  Service,
  Product,
  Client
} from './entity/index';

const myContainer = new Container();
myContainer.bind<ServiceInterface>(diTypes.service).to(Service);
myContainer.bind<ProductInterface>(diTypes.product).to(Product);
myContainer.bind<ClientInterface>(diTypes.client).to(Client);

export { myContainer };
index.ts
import 'reflect-metadata';
import { myContainer } from './inversify.config';
import { diTypes } from './types';
import { ClientInterface } from './interface';

const ohteru = myContainer.get<ClientInterface>(diTypes.client);

export default () => {
  ohteru.doSomeThing();
  ohteru.useSomething();
}

考察とまとめ

がっつりデコレーターを使って実装してますね
個人的にはブラックボックス化されていてデコレーターは好きじゃないですが
便利なものが多く準備されていることに間違いはないです

なので今回はDIコンテナを使いましたが、自分でDIコンテナを実装してみた方がいいなと思いました

また、僕は実際の業務ではdiは使っていませんが、依存性について意識して製作することによって
柔軟性の高い設計の仕方を学ぶことができました

もっと良いコードについて勉強していこうと思います!

次回は@kooooooさんの記事です!お楽しみに!

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
What you can do with signing up
2