【オブジェクト指向設計実践ガイド】依存関係編

# はじめに

[【オブジェクト指向設計実践ガイド】SRP編](https://qiita.com/EbiTT/items/37f96825f65f23325b5e)の続き。
今回は異なるオブジェクト間の依存関係をいい感じにする方法について考えていきます。

# 特定のオブジェクトのインスタンスに依存しないこと

特定のクラスのインスタンスに依存するコードの例を出してみましょう。人間を表すHumanクラスの他にTwitterのユーザを表すTwitterUserクラスを追加し、新たにHumanクラスに呟くメソッドを足してみましょう。

```ruby
+ class TwitterUser
+     def tweet
+         # ... Twitterでツイートする実装
+     end
+ end

class Human
    # これはデータ
    attr_reader :weight, :height
    def initalizer(:weight, :height)
        @weight = weight
        @height = height
    end

    # ツイートするメソッド
+    def tweet
+        TwitterUser.new().tweet
+    end

    # 適正体重を求めるメソッド(= 適正体重を返すという振る舞い)
    def desirable_weight
        (height ^ 2) * 22
    end

    # BMIを求めるメソッド
    def bmi
        weight + (height ^ 2)
    end
end
```

さて、ツイートするメソッドがTwitterUserクラスにいて良いのかというのは一旦置いておきましょう。今問題なのは、HumanクラスからTwitterUserクラスという特定のクラスのインスタンスへの依存が生じている事です。以下のような依存と懸念があります。


- tweetメソッドを持つクラスの名前が `TwitterUser` であることを `Human` が知っている。
    - もし `TwitterUser` にクラス名の変更が生じた場合に、呼び出し側も変更しないといけない
    - もし `TwitterUser` クラスを削除することになったら `Human` のtweetメソッドも対応を迫られる
- `Human` は `TwitterUser` がtweetメソッドを持っていることを知っている
- もし `TwitterUser` クラスがコンストラクタに引数を取るように変更されたら `Human` の `TwitterUser.new()` も対応しないといけない。
    - その場合、 `Human` は `TwitterUser` がどのような引数をどんな順番でコンストラクタに取るのかも知ることになる
    - `TwitterUser`の変更がしにくく、Humanクラスが弱くなっている。

そして何よりも ***「TwitterUser以外でツイートすることができなくなっている」***

...ということで特定のオブジェクトのインスタンスに依存するのはアンチパターンです。じゃあどうすりゃいいのか。


# 「メッセージ」こそが重要

オブジェクト同士が関わり合って何かする時に重要なのは、 ***「どのようなメッセージを送るか」*** という事です。送り先が何のオブジェクトかということはどうでもいいのです。これを実践し、さっきの質問への答えとなるのが ***「依存性注入」*** です。やってみましょう。

```ruby
 class TwitterUser
     def tweet
         # ... Twitterでツイートする実装
     end
 end

class Human
    attr_reader :sns
+    def initalizer(:weight,:height,:sns)
        @weight = weight
        @height = height
+       @sns = sns
    end

    # ツイートするメソッド
    def tweet
+        sns.tweet
    end
end
```

見やすいようにツイート関連以外のコードは消しました。そして、 `Human` の `tweet` メソッドは `tweet` メソッドを持つような `sns` オブジェクトを呼び出すだけになっています。 `Human` クラスが依存しているオブジェクト(この場合は `TwitterUser` ) をコンストラクタで「注入」しているので依存性注入というのですね。僅かな変更ですが嬉しいことがあります。

- `Human` はtweetメソッドを持つオブジェクトであれば、どんなオブジェクトを `sns` にとることもできる。
    - 例えば自前のSNSのツイート機能を `Human` クラスに組み込むことも今や簡単です。
    - そして `Human` は `sns` に 「ツイートせよ」というメッセージを送るだけで良くなっており、より本質的なコードになっています。

# 依存は隔離せよ

さて、依存性注入の威力はわかりましたがいつもこう上手くできるとは限りません。特に、既にスパゲッティコードが大量に乗っているようなコードベースの場合、あるいは注入すべき上手い抽象が見つからない場合などにです。

しかし完璧に依存性注入を適用できなくても、コードをマシなものにすることはできます。 それが ***「依存を隔離する」*** ということです。
これを実践した例を先ほどの `Human` と `TwitterUser` で考えてみましょう。

```ruby
 class TwitterUser
     def tweet
         # ... Twitterでツイートする実装
     end
 end

class Human
    attr_reader :sns
+    def initalizer(:weight,:height)
        @weight = weight
        @height = height
        # コンストラクタで初期化
+       @sns = TwitterUser.new()
    end

    # ツイートするメソッド
    def tweet
        sns.tweet
    end

    # あるいはこう書くこともできる
    def sns
        @sns ||= TwitterUser.new()
    end
end
```

`Human` のコンストラクタ内やメソッドで直接 `TwitterUser` を初期化しています。当然、 `TwitterUser` という特定のオブジェクトへの依存が`Human` の中に生じることになるので、ベストなコードとは言えません。しかし、この記事の冒頭のコードに比べて改善したこともあります。

- `Human` が `TwitterUser` に依存している問題がコード上で明確になった。
- 今後リファクタリングできるようになった時には、 `Human` のコンストラクタだけを修正すれば良い

冒頭のコードではtweetメッセージを外部の `TwittterUser` に直接送っていましたが、メッセージを自身のインスタンス変数の読み取りメソッドに送るようにしたことによってオブジェクト間の結合が弱まっています!

---

しかしこれではまだ不十分です。このコードの場合は `Human` のtweetメソッドが `TwitterUser` にtweetメソッドがあることを知っています。
よりマシなコードにするのであれば、以下のように `TwitterUser` オブジェクトにメッセージを送っている部分をラップし、ラッパーを呼び出すようにします。こうすることで

- `TwitterUser` のtweetメソッドの名前やシグネチャが変更された場合でも、影響はラッパー(この場合はtwitter_tweetメソッド)の中に収まります。

```ruby
 class TwitterUser
     def tweet
         # ... Twitterでツイートする実装
     end
 end

class Human
    attr_reader :sns
    def initalizer(:weight,:height)
        @weight = weight
        @height = height
        # コンストラクタで初期化
       @sns = TwitterUser.new()
    end

    # ツイートするメソッド
    def tweet
+        twitter_tweet
    end

+    # TwitterUserをラップする
+    def twitter_tweet
+        sns.tweet
+    end
end
```

ということで依存性注入できないならせめて ***「依存は隔離せよ」***

# 引数やインデックスの順番に依存しないこと

さて、ここでは引数の順番に関する依存について改善していきましょう。引数を持つような関数を呼び出す側は、関数のシグネチャが変更されると常に対応を迫られます。これにはいくつかの対処法があります。


- 引数をハッシュ(JSやPHPでいう連想配列、Pythonでいう辞書、Goでいうmap)で受け取る
    - あるいは必須の引数だけ普通に受け取り、オプショナルな引数をハッシュで受け取る
- 引数にデフォルト値を設定しておく
- 外部ライブラリのオブジェクトを初期化するなら、初期化だけのためのラッパー(いわゆるファクトリ)を実装する