実務の中でDIについて考えるきっかけがあったので、学んだことをまとめてみました
DI -依存性の注入-とは?
DIの基本的な考え方
あるコンポーネントが他のコンポーネントを使用する際に、そのインスタンスを自ら生成するのではなく、外部から提供してもらう
DI学んだきっかけになったレビュー
先輩から以下のようなレビューをいただき、DIについて学ぶきっかけになりました
レビュー前のコード
- あるクラス(以下の
XxxCommand
)の引数として、オブジェクトResourceA、ResourceBのidを受け取るように実装 - 受け取ったidを元に、クラス内でResourceAおよびResourceBのインスタンスを生成
class XxxCommand
def initialize(resource_a_id:, resource_b_id:)
@resource_a = ResourceA.find_by(id: resource_a_id)
@resource_b = ResourceB.find_by(id: resource_b_id)
end
def run
# 処理ロジック
end
end
困ったこと
- テストが書きづらい
-
XxxCommand
クラスの中で直接インスタンス化しているので、ResourceA
クラスやResourceB
クラスの実装に依存したテストとなってしまう - その結果、コード内で依存関係が固定されてしまい、モックに差し替えることができない
-
レビュー後のコード
- クラス内で生成するのではなく、引数としてResourceAおよびResourceBのオブジェクトを直接受け取るように
- これにより、
ResourceA
クラスやResourceB
クラスの実装に依存せず、テスト時にモックやスタブを渡すことができるように
class XxxCommand
def initialize(resource_a:, resource_b)
@resource_a = resource_a
@resource_b = resource_b
end
def run
# 処理ロジック
end
end
上記のレビューをきっかけに、なんとなく聞いたことがあったDIについて学ぶことになりました。以下学んだことをまとめていきます。
以下の参考文献をもとに学習しました
そもそも依存とは?
プログラムにおける依存(Dependency)とは、あるクラスやモジュールが、他のクラスやモジュールを使用することで生じる関係性のことを指す。
例えば、ユーザー情報を扱うクラスがデータベースアクセスを行うクラスを使用する場合、ユーザー情報管理クラスはデータベースアクセスクラスに依存していると言える。
依存はプログラミングをしている限り、日常的に発生するものだが、以下のようなリスクを内包する。
依存に潜む問題点
- クラスAの変更が、クラスAに依存しているクラスに影響を与える可能性がある
- ユニットテストを行う際に、モック化が困難になる
DIが解決すること
バリエーションの拡張
- DIをインターフェースなどの抽象化と組み合わせることで、依存オブジェクトを柔軟に変更ができる
- これにより、さまざまなバリエーションに対応することが可能になる
共通処理の呼び出し
* 複数のオブジェクトで共通して使用される処理を依存オブジェクトとして注入できる
* これにより、コードの重複を減らし、保守性を高めることができる
オブジェクトの生成と使用の分離
- オブジェクトの生成と使用を分離できる
- これによりオブジェクトの生成方法を変更しても、オブジェクトの使用する側のコードに影響を与えないようになる
適切な責務分割が可能に
こちらの記事のCar
クラスとEngine
クラスの考え方がとても分かりやすかったので、引用させていただきます。
簡単にまとめると、
DIを行う前
-
Car
クラス自身が内部でEngine
インスタンスを生成する - 「車の作成」の中で、「エンジンの作成」まで責務を持っている状態
DIを行った後
-
Car
クラスは、外部からEngine
インスタンスを受けとるように - 「車の作成」時に、部品としてエンジンを受け取るだけに
- エンジンの作成方法などは、知る必要がない状態になる(責務外になる)
もっと上手く言語化できるように、さらに理解が深まったら資料を更新しようと思います。
ここまで記事を読んでくださりありがとうございます。コメントなど大歓迎ですので、気になったことなどございましたらコメントお願いします。