5
2

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 3 years have passed since last update.

株式会社CAMAdvent Calendar 2019

Day 9

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

Last updated at Posted at 2019-12-08

こちらは、 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さんの記事です!お楽しみに!

5
2
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
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?