依存性逆転の原則は SOLID 原則の一部であり、特に高レベルのアルゴリズムのテスト容易性と再利用を促進するための重要な原則です。
背景: Uncle Bob の Web ページに示されているように、Dependency Inversion は具象ではなく抽象化に依存するものです。
実際には、クラスが別のクラスを直接インスタンス化するいくつかの場所を変更して、内部クラスの実装を呼び出し元が指定できるようにする必要があります。
たとえば、Model クラスがある場合、特定のデータベース クラスを使用するようにハード コードするべきではありません。その場合、Model クラスを使用して別のデータベース実装を使用することはできません。これは、別のデータベース プロバイダーを使用している場合や、テスト目的でデータベース プロバイダーを偽のデータベースに置き換えたい場合に役立ちます。
Model が Database クラスで「新規」を実行するのではなく、Database クラスが実装する IDatabase インターフェイスを使用するだけです。モデルは具体的なデータベース クラスを参照することはありません。しかし、データベース クラスをインスタンス化するのは誰でしょうか? 1 つの解決策は、コンストラクター インジェクション (依存性インジェクションの一部) です。この例では、Model クラスには、それ自体をインスタンス化するのではなく、使用する IDatabase インスタンスを取る新しいコンストラクターが与えられます。
これにより、モデルの元の問題が解決され、具体的なデータベース クラスが参照されなくなり、IDatabase 抽象化を通じてデータベースが使用されます。しかし、それはデメテルの法則に反するという、質問で言及された問題をもたらします。つまり、この場合、Model の呼び出し元は、以前は認識していなかった IDatabase について知る必要があります。モデルは現在、その仕事をどのように遂行するかについての詳細をクライアントに公開しています。
これで大丈夫だとしても、一部のトレーナーを含む多くの人々を混乱させるように見える別の問題があります. モデルなどのクラスが別のクラスを具体的にインスタンス化するときはいつでも、依存関係の逆転の原則に違反しているため、それは悪いことであるという前提があります。しかし、実際には、このような厳格なルールに従うことはできません。具象クラスを使用する必要がある場合があります。たとえば、例外をスローする場合は、「新しいものにする」必要があります (たとえば、新しい BadArgumentException(...) をスローしました)。または、文字列、辞書などの基本システムのクラスを使用します。
すべてのケースで機能する単純なルールはありません。達成しようとしていることが何であるかを理解する必要があります。テスト容易性を追求している場合、Model クラスが Database クラスを直接参照すること自体は問題ではありません。問題は、Model クラスには別の Database クラスを使用する他の手段がないという事実です。IDatabase を使用するように Model クラスを実装し、クライアントが IDatabase 実装を指定できるようにすることで、この問題を解決します。クライアントによって指定されていない場合、モデルは具体的な実装を使用できます。
これは、C++ 標準ライブラリを含む多くのライブラリの設計に似ています。たとえば、宣言 std::set コンテナーを見ると、次のようになります。
template < class T, // set::key_type/value_type
class Compare = less<T>, // set::key_compare/value_compare
class Alloc = allocator<T> > // set::allocator_type
> class set;
コンペアラーとアロケーターを指定できることがわかりますが、ほとんどの場合、デフォルト、特にアロケーターを使用します。STL には、特にローカリゼーション、エンディアンネス、ロケールなどのためにストリーミングの詳細な側面を拡張できる IO ライブラリに、そのような多くのファセットがあります。
テスト容易性に加えて、これにより、アルゴリズムが内部で使用するクラスの完全に異なる実装を使用して、上位レベルのアルゴリズムを再利用できます。
そして最後に、依存関係を逆転させたくないシナリオに関して以前に行った主張に戻ります。つまり、例外クラス BadArgumentException をインスタンス化する場合など、具象クラスをインスタンス化する必要がある場合があります。ただし、テスト容易性を追求している場合は、実際にはこれの依存関係を逆にしたいと主張することもできます。例外のすべてのインスタンス化がクラスに委任され、抽象インターフェイスを介して呼び出されるように Model クラスを設計することができます。そうすれば、Model クラスをテストするコードは、テストで使用状況を監視できる独自の例外クラスを提供できます。
「getsystemtime」などのシステム コールのインスタンス化を単純に抽象化して、単体テストを通じて夏時間とタイム ゾーンのシナリオをテストできるようにする例を同僚に教えてもらいました。
YAGNI の原則に従います。抽象化が必要かもしれないという理由だけで抽象化を追加しないでください。テスト ファーストの開発を実践している場合、適切な抽象化が明らかになり、テストに合格するのに必要なだけの抽象化が実装されます。