Dependency injectionは良いコードへのアプローチ

springを使い始めてDependency injectionとはなにかいまいちつかめない方向けに書きます。

  • ほかの実装方法もある中、何故Dependency injectionをつかうのかわからない
  • 依存性注入という名前にピンとこない

この、わかりづらさは

  • Dependency injectionが必要になるまでの流れが見えない
  • SpringにおけるDependency injectionの省略

が組み合わさっていると考えました。

私自身は何故使用するのかを理解するまでには時間を費やしました。
自分なりに消化できた部分をこの記事で紹介します。

それでは本文です。

Dependency injectionがある理由→良いコード

Dependency injectionは良いコードを書くためのアプローチです。

良いコードは皆さんそれぞれ思うことがあるとは思いますけれども、
この場ではよくテストしている場合とお考えください。

  1. 良いコードは、よくテストされている
  2. テストするには、独立性が高い(小さくテストできる)
  3. 独立性を上げるために、分解をする
  4. 分解しすぎたので、実行するときにくっつける←ここがDependency injection

つまり依存性注入ということがしていることは良いコードを書くための最後のところだけということです。
最後だけみてしまうと「やっていること(how)はわかる、しかし何故やっているのか(why)がわからん」となるのです。

では、Dependency injectionが出てくるまでを順番にみていきましょう。

単体テストをするにはクラスごとに分けたい

まずはこちらをご覧ください。データベースにアクセスするクラスと、それを呼び出すだけのクラスとメソッドです。

このコードは何の実行するには変哲もないコードですけれども、
単体テストを使用とすると困ったことがおきます。

仕組上、SomeMethodsを呼び出すと必ずDbAccsssのクラスが作られてそのメソッドが呼ばれます。
つまり、テストをしようとしたときに、もれなくDbAccsssもテストされることになります。

一石二鳥じゃんという場合と、
問題が起きたときに、SomeClassとDbAccessのどちらに原因があるのかわからないと困る場合と、
があります。

DependencyInjectionを理解するには後者の考え方を採用します。

あくまでSomeClassはSomeClassだけでテスト、DbAccessはDbAccessだけでテストすることで、
より良いコードにすることが目的になります。

インターフェースとコンストラクタでクラスを分離する

では、密接につながっている二つのクラスを分断するにはどうするか。
そこで登場するのがインターフェースとコンストラクタです。

setterを利用するパターンもありますけれども、今回はコンストラクタの場合を紹介します。

まず、メソッドのなかでクラスを生成しているという「触れにくい」部分を表面に出すために、
コンストラクタで生成したクラスを受けれるようにします。

次に、インターフェースを間に挟むことでDbAccessとSomeClassを分離します。

インターフェースが共通のダミークラスでテスト

用意したインターフェースをもつダミークラスを用意します。

すると、DbAccessを呼び出さずにSomeClassのコード部分だけをテストすることができるようになりました。

では実行時はどうするのか、です。

実行時に活躍するDependency injection

いま、インターフェースで分離してしまったがゆえに今度は、2つのクラスをくっつけて生成するコードがなくなってしまっています。
ここでDependency injectionの登場です。
実行時にはSomeMethodsでDbAccsssがきちんと使われるようにします。

これで実行時にcreateSomeClass()メソッドを呼び出せば、
インターフェースで分離された二つのクラスがきちんと関連づいた状態で生成されます。

SomeClassとDbAccessとの間に関連性(依存性)ができました。

依存性注入です。

springは強力なDependency injectionツール

ここまで来て手元のspringのコードと違うと思われるでしょう。
springの場合はこの関連付けるコードすら出てこないのではないでしょうか。

おそらくは次の2点の違いがあります。

  • 依存性注入しているコードがない
  • インターフェースを使用していない(コンストラクタだけ)

springをはじめとするは強力なツールゆえに省略されているからです。
ひとつずつ見ていきます。

アノテーションによるコード省略

結論からいうと、依存性注入しているコードの代わりにアノテーションが対応しています。
Dependency injectionに関連するアノテーションは
@Component,@Service…etc.
など役割によって使い分けられるように複数用意されています。
ただ、Dependency injectionする上では基本同じ機能です。

アノテーションを付与したクラスはcreateSomeClass()に相当する処理が自動で行われるため、
エンジニアは自分たちでクラスを関連付けるコードを書く必要がなくなるのです。

ただし、この優秀な自動化は便利な反面、初見さんの混乱のもとにもなります。

springのコードを初めてみたときには、アノテーションによりコードを省略されているので、なぜ動くのかわからないという状態になりやすいです。

インターフェースが無くてもモックライブラリがあればOK

続いてインターフェースを使用していない件についてです。

実はコンストラクタさえあれば、インターフェースが無くても依存性注入が可能になります。
createSomeClass()メソッドはインターフェースが無くても問題なく動くでしょう。
ただし、この場合はクラスを生成することになるので単体テストができないという問題が生じます。

ですが、この問題の解決策はすでにあります。
mockitoなどのモックライブラリの利用です。

モックライブラリを使用できれば、インターフェースを利用しなくてもダミーのインスタンスを渡すことができます。
すると本体コードからはインターフェースの記述がなくなり、単体テストができる状況になるため、
インターフェース分のコード製造のコストが少なくなるメリットを得られます。

まとめ

改めて依存性注入が要る理由をまとめます。

なぜ依存性注入を使うのか、分解しすぎてしまっているため
なぜ分解しすぎているのか、独立性を高めるため
なぜ独立性を高めるのか、単体テストをするため
なぜ単体テストをするのか、良いコードを書くため

あくまで一個人の理解の内容になりますけれども、
誰かの参考になれば幸いです。

では。