社内の勉強会で講師をするにあたり、DIについて知識の再確認をしたかったので読んでみました。
何のためにDIを使うべきなのかの理解があいまいな方、DIにより何を疎結合にするべきかの基準があいまいな方の一助になればと思います。
概要
コード例はC#
読むのにさほど問題無いとは思いますが、コード例はC#です。
一部に.NET特有の機能を使用したコードもありますが、知識がなくても理解できるレベルです。
DIの基本
DI(Dependency Injection)とは
本書での定義は「さまざまなソフトウェア設計の原則やパターンを集めたものであり、コードが疎結合となるような開発を行えるようにするもの」です。
(この定義だけでは、オブジェクトの依存関係と無関係な設計、パターンも含むことになりますので
上記の目的を満たすために依存を注入する設計/パターンを指すと考えるべきと思います)
DIの目的
DIによってインタフェースを介して依存する設計にし、オブジェクト間の関係を疎結合にすることで保守容易性を高めることが目的となります。
それにより、Composite、Adapter、Decorator、Nullオブジェクトのようなパターンを導入しやすくし、
開放/閉鎖の原則に従い、既存コードを変更せずに機能拡張を行えるようにできます。
また上位モジュールに下位モジュールへのインタフェースを注入することで、
依存関係逆転を実現することができます。
DIへの誤解
DIに対しては以下の誤解があります。
- 再コンパイルなしで機能を切り替える遅延バインディングのために使うもの
- Unitテストを行いやすくするためだけに使うもの
- Abstract Factoryパターンの亜種であり、必要なオブジェクトを生成するためのもの
- DIコンテナが必須であるというもの
何を注入するのか?
すべての依存を疎結合にするのは現実的でありません。
例として、標準ライブラリへの依存まで疎結合にしても意味がありません。
対象となる依存が安定依存か揮発性依存なのかを分類し、
揮発性依存に対して疎結合にするため、DIを検討する必要があります。
安定依存
標準ライブラリ等、以下の様な性質の依存は安定依存となります。
(これは例でありこれが全てではない)
- 最初から存在している
- APIの互換性が保証されることを期待できる
- 同じ入力に対して同じ結果を返す
- そのクラスやモジュールの置き換え、Decoratorパターンの適用を行うことがない
揮発性依存
対して、以下の条件のいずれかを満たすものは揮発性依存となります。
- 対象の依存を導入して稼働させるのに、実行環境による設定、調整が必要
- 対象の依存となるクラスがまだ存在しない(開発中等)
- 対象の依存が開発に関わる全環境で用意されていない
- 同じ入力に対して異なる結果を返すことがある(現在時刻、乱数等)
DIのパターン
DIを行う場合、注入される側のクラスは抽象を参照し、実装クラスをコンストラクタで注入するのが基本です。
実装クラスの生成(new)は合成起点といわれるアプリケーションのエントリポイント(mainメソッド等、アプリが起動して最初に動く処理)に近い部分で実行するのが基本となります。
設計によるアスペクト指向プログラミング
横断的な関心事(全Webリクエストに対する認証、ログ出力等)に対して
ボイラープレートのコードを減少させることを目的としたプログラミングの考え方です。
通常、アスペクト指向を実現するにはDIコンテナ等のツールを使いますが、
本書では設計による実現方法を提示しています。
- 同種の横断的関心事の対象になるクラス群を1つのpublicメソッドだけを持つ様にクラスを分離する
- 各クラスのpublicメソッドをインタフェースとして抽出する
- 各メソッドの引数を1つのクラスに集約するように変更する
- インタフェースの引数、戻り値をジェネリクス等を用いて共通化し、すべてのクラスが1つのインタフェースを実装するように変更する
この方法で、横断的関心事に対して、合成起点でDecorator等を用いることで介入が可能になります。
DIコンテナ
DIコンテナはDIを行うのに必須ではなく、手動で行うことが可能です。
DIコンテナ導入のコストとリスク、
依存関係の設定等の煩雑さ(理想的には自動で依存関係が解決されるべき)
に対して正当化できる場合、DIコンテナを用いるべきです。
感想
おおむね良い内容であり、読む価値があったと思います。
DIの基本/目的
違和感の無い内容でした。今まで書籍や経験を通して学んだ設計方針を明確にしてくれた感じです。
また、DIの導入にDIコンテナは必須ではない、という主張も同意します。
DIコンテナはDIを容易にしてくれるだけですので、DIを用いた疎結合な設計は開発者自身が考えなければならないものです。
安定依存と揮発性依存
何をDIによって疎結合にするかの基準を明確化してくれています。
ただ、ControllerとService(UI層とドメイン層の接合部)に対して、Serviceがまだ存在しないから揮発性依存である、としているのは違和感がありました。
関心事が異なる層の接合部に対しては、層間の結合を最小限にするためやテスト容易性を高めるためにDIを使うべきと思います。
そういった議論が欲しいと感じました
DIのパターン
コンストラクタでの注入が基本、というスタンスも違和感はないです。
設計によるアスペクト指向
提案されている設計方法は僕も考えたことはありますが、実際に使用したことはないです。
ここまで横断的関心事を考慮するのであれば、DIコンテナ等のフレームワークを導入するべきと思いますし、
クラスの細分化等のデメリットが大きくなりそうです。
ログ出力や認証程度の関心事であれば、アノテーションなりフレームワークの機能で十分に実現できますので
そのために設計を大幅に変える必要は無いように思います。
DIコンテナ
これは難しいところです。
本書で提示されているような疎結合な設計を実現するのであれば、
DIコンテナを含むフレームワークを導入することで大幅に開発効率が向上すると思います。
ある程度の規模のアプリケーションを実装するのであれば、
疎結合な設計を行うべきであり、その場合にはDIコンテナのメリットを生かせるケースが多いはずです。
ある程度の規模のアプリケーションを実装していてDIコンテナのメリットを生かせないとすると、
そもそも疎結合な設計を行っていない(もしくは出来ていない)状況の可能性が高いです。