🏹

依存性の逆転をクラスなしで実現する方法

2023/12/20に公開

はじめに

前回自分が書いた記事で依存性の逆転について説明したところ、じわじわいいねが増えて嬉しかったため、今回はその続編です。

前回の記事で説明した依存性逆転のテクニックはオブジェクト指向の特権のように思われがちですが、実はそうではありません。
むしろクラスを使わない関数型スタイルの方が簡潔に表現できます。

今回はそれを示すために、前回の説明をTypeScriptでクラスを使わずに行います。

依存性の逆転のやり方(関数バージョン)

例えば関数Aが関数Bに依存している状況を考えます。(クラス図を関数の依存関係と読み替えてください)

具体例: 関数Aは挨拶するとき、関数Bに挨拶の仕方を教えてもらって挨拶している

const funcB = () => {
  return "こんにちは!";
};

const funcA = () => {
  const greetMessage = funcB();

  console.log(greetMessage);
};

funcA();

この依存関係を次のように逆転したいとします。

これを行うには、次のことをします。

  • 関数Bの型IBを用意する
  • IBを満たす関数Bを宣言する
  • AはIBに依存させる

すると以下のようになります。

このIBをAの一部ととらえたとき、BはAの一部であるIBに依存して実装を行っています。つまり、AとBの依存関係は逆転しているといえます。

具体例

A.ts
export type IB = () => string;

export const funcA = (func: IB) => {
  const greetMessage = func();

  console.log(greetMessage);
};
B.ts
import { IB } from "A.ts"

export const funcB: IB = () => {
  return "こんにちは!";
};
main.ts
import { funcA } from "A.ts"
import { funcB } from "B.ts"

funcA(funcB)

なお今回はfuncA(funcB)のように、関数呼び出し時に依存するものを外から渡してあげています。
これも立派な 依存性の注入(Dependency Injection) です。

関数Aに関数Bを毎回渡すのは面倒

このままでは関数Aを呼び出す度に関数Bを渡してあげなければいけません。これは少し面倒です。

そんなときは、以下のように関数Bを受け取り関数Aを返す関数を作ります。

A.ts
export type IB = () => string;

export const buildFuncA = (func: IB) => {
  const funcA = () => {
    const greetMessage = func();

    console.log(greetMessage);
  };

  return funcA;
};

これを利用すると、以下のように関数Bを引数に受け取らない関数Aを作り出すことができます。

main.ts
import { buildFuncA } from "A.ts"
import { funcB } from "B.ts"

const funcA = buildFuncA(funcB)

funcA()
funcA()

このようにして関数の引数を事前に受け取れるようにするテクニックをカリー化といいます。

これと前回の記事のクラスバージョンのmain.tsを見比べてみてください。

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()

クラスがコンストラクタを介して事前に依存性の注入を行っているように、関数でもカリー化を用いて事前に依存性の注入を実現できます。

ちなみにカリー化された関数はもっと省略した書き方があります。

A.ts
export type IB = () => string;

export const buildFuncA = (func: IB) => () => {
  const greetMessage = func();

  console.log(greetMessage);
};

最後に

今回は依存性の逆転をクラスを使わない関数型スタイルでTypeScriptを用いて実現してみました。実はC言語でも頑張れば同様のことは実現できます。依存関係逆転の原則を守れるのは何もオブジェクト指向言語の特権ではないのです。
これは依存関係逆転の原則に限った話ではなく、今までオブジェクト指向について言われてきた様々な利点について言えることです。
これからは関数型スタイルの書き方がオブジェクト指向よりシンプルで保守性が高いことに気づかれていくのではないかと思います。

Discussion