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