依存性の逆転をクラスなしで実現する方法
はじめに
前回自分が書いた記事で依存性の逆転について説明したところ、じわじわいいねが増えて嬉しかったため、今回はその続編です。
前回の記事で説明した依存性逆転のテクニックはオブジェクト指向の特権のように思われがちですが、実はそうではありません。
むしろクラスを使わない関数型スタイルの方が簡潔に表現できます。
今回はそれを示すために、前回の説明をTypeScriptでクラスを使わずに行います。
依存性の逆転のやり方(関数バージョン)
例えば関数Aが関数Bに依存している状況を考えます。(クラス図を関数の依存関係と読み替えてください)
具体例: 関数Aは挨拶するとき、関数Bに挨拶の仕方を教えてもらって挨拶している
// 挨拶の仕方を取得する関数B(アロー関数で表記)
const funcB = () => {
return "こんにちは!";
};
// コンソールに挨拶を出力する関数A(アロー関数で表記)
const funcA = () => {
const greetMessage = funcB(); // 関数Bを用いて挨拶の仕方を取得
console.log(greetMessage); // 取得した挨拶を出力
};
funcA(); // 関数Aを実行
この依存関係を次のように逆転したいとします。
これを行うには、次のことをします。
- 関数Bの型IBを用意する
- IBを満たす関数Bを宣言する
- AはIBに依存させる
すると以下のようになります。
このIBをAの一部ととらえたとき、BはAの一部であるIBに依存して実装を行っています。つまり、AとBの依存関係は逆転しているといえます。
具体例
// 関数Bの型IB
export type IB = () => string;
// 引数で型IBの実体を受け取る
export const funcA = (funcB: IB) => {
// 受け取った型IBの実体を用いる
const greetMessage = funcB();
console.log(greetMessage);
};
import { IB } from "A.ts"
// 型IBの実体を実装
export const funcB: IB = () => {
return "こんにちは!";
};
import { funcA } from "A.ts"
import { funcB } from "B.ts"
// funcBはfuncAの呼び出し時に引数に渡す
funcA(funcB)
なお今回はfuncA(funcB)
のように、関数呼び出し時に依存するものを外から渡してあげています。
これも立派な 依存性の注入(Dependency Injection) です。
関数Aに関数Bを毎回渡すのは面倒
このままでは関数Aを呼び出す度に毎回関数Bを渡してあげなければいけません。これは少し面倒です。
そんなときは以下のように関数Bの実体を受け取り、関数Aを作り出す関数を定義します。
// 関数Bの型IB
export type IB = () => string;
// 関数Aを返す関数
export const constructFuncA = (funcB: IB) => {
// 関数の内部で関数Aを生成
const funcA = () => {
// 外側の関数で受け取ったfuncBの実体を用いる
const greetMessage = funcB();
console.log(greetMessage);
};
return funcA; // 関数Aを返す
};
これを利用すると、以下のように関数Bを引数に受け取らない関数Aを作り出すことができます。
import { constructFuncA } from "A.ts"
import { funcB } from "B.ts"
const funcA = constructFuncA(funcB) // 関数Aを生成
funcA() // 引数無しで関数を実行
funcA()
このようにして関数の引数を事前に受け取れるようにするテクニックをカリー化といいます。
これを前回の記事のクラスバージョンのmain.ts
と見比べてみてください。
import { A } from "A.ts"
import { B } from "B.ts"
const b = new B() // Bは外で生成して渡す
const a = new A(b)
a.greet()
a.greet()
クラスがコンストラクタを介して事前に依存性の注入を行っているように、関数でもカリー化を用いて事前に依存性の注入を実現できます。
ちなみにカリー化された関数はもっと省略した書き方があります。
export type IB = () => string;
export const constructFuncA = (func: IB) => () => {
const greetMessage = func();
console.log(greetMessage);
};
最後に
今回は依存性の逆転をクラスを使わない関数型スタイルでTypeScriptを用いて実現してみました。実はC言語でも頑張れば同様のことは実現できます。依存関係逆転の原則を守れるのは何もオブジェクト指向言語の特権ではないのです。
これは依存関係逆転の原則に限った話ではなく、今までオブジェクト指向について言われてきた様々な利点について言えることです。
これからは関数型スタイルの書き方がオブジェクト指向よりシンプルで保守性が高いことに気づかれていくのではないかと思います。
Discussion