動機
やる気
ソフトウェアアプリケーションを設計するとき、低レベルのクラスを基本および基本操作(ディスクアクセス、ネットワークプロトコルなど)を実装するクラス、高レベルのクラスを複雑なロジック(ビジネスフローなど)をカプセル化するクラスと見なすことができます。最後のものは低レベルのクラスに依存しています。このような構造を実装する自然な方法は、低レベルのクラスを記述し、それらを実装したら、複雑な高レベルのクラスを記述することです。高レベルのクラスは他のクラスで定義されているため、これは論理的な方法のようです。しかし、これは柔軟な設計ではありません。低レベルのクラスを置き換える必要がある場合はどうなりますか?
ソフトウェアを設計するとき、それは低レベルのクラスと高レベルのクラスに分けることができます。低レベルのクラスは基本および主要な操作(ディスクアクセス、ネットワークプロトコルなど)の実装に使用され、高レベルのクラスは複雑なロジック(ビジネスフロー... 。)、高レベルのクラスは低レベルのクラスに依存しています。このような構造を実装する自然な方法は、最初に低レベルのクラスを記述し、次にそれらを使用して複雑な高レベルのクラスを実装することです。他のクラスに基づく高レベルのクラスの実装は論理的なようですが、この設計は十分に柔軟ではありません。低レベルのクラスを置き換える場合はどうでしょうか。
キーボードから文字を読み取り、プリンターデバイスに書き込むコピーモジュールの古典的な例を見てみましょう。ロジックを含む高レベルのクラスは、コピークラスです。低レベルのクラスは、KeyboardReaderとPrinterWriterです。
古典的な例を見てみましょう。コピーモジュールがキーボードから文字を読み取り、出力デバイスに書き込みます。ロジックを含む高レベルのクラスはCopyクラスであり、低レベルのクラスはKeyboardReaderとPrinterWriterです。
悪い設計では、高レベルのクラスが直接使用し、低レベルのクラスに大きく依存します。そのような場合、出力を新しいFileWriterクラスに送るようにデザインを変更する場合は、Copyクラスを変更する必要があります。(それは非常に複雑なクラスであり、多くのロジックがあり、テストが本当に難しいと仮定しましょう)。
不適切な設計では、高レベルのクラスが直接使用し、低レベルのクラスに依存します。この場合、デザインを変更して新しいFileWriterクラスに出力する場合は、Copyクラスを変更する必要があります。(Copyクラスは、内部に多くのロジックを含み、テストが難しい複雑なクラスであると想定しています)。
このような問題を回避するために、高レベルのクラスと低レベルのクラスの間に抽象化レイヤーを導入できます。高レベルのモジュールには複雑なロジックが含まれているため、低レベルのモジュールに依存してはならず、低レベルのモジュールに基づいて新しい抽象化レイヤーを作成しないでください。低レベルのモジュールは、抽象化レイヤーに基づいて作成されます。
このような問題を回避するために、高レベルのクラスと低レベルのクラスの間に抽象化レイヤーを導入します。複雑なロジックを含む高レベルのクラスは低レベルのモジュールに依存する必要がないため、新しい抽象レイヤーは、それらを作成するために低レベルのモジュールに依存する必要はありません。低レベルのモジュールは、抽象化レイヤーに基づいて作成されます。
この原則に従って、クラス構造を設計する方法は、高レベルのモジュールから低レベルのモジュールへと開始することです。
高レベルのクラス->抽象化レイヤー->低レベルのクラス
この原則に従って、クラス構造の設計は、高レベルのモジュールから低レベルのモジュールに始まります。
高水準クラス->抽象層->低水準クラス
意図
目的
- 高レベルのモジュールは、低レベルのモジュールに依存するべきではありません。どちらも抽象化に依存する必要があります。
- 抽象化は詳細に依存すべきではありません。詳細は抽象化に依存する必要があります。
- 高レベルのモジュールは低レベルのモジュールに依存するべきではなく、それらはすべて抽象化に依存するべきです。
- 抽象化は詳細に依存するべきではなく、詳細は抽象化に依存するべきです。
例
例
以下は、依存関係の逆転の原則に違反する例です。高レベルのクラスであるマネージャークラスと、ワーカーと呼ばれる低レベルのクラスがあります。新しい専門の労働者の雇用によって決定された企業構造の変化をモデル化するために、アプリケーションに新しいモジュールを追加する必要があります。このための新しいクラスSuperWorkerを作成しました。
以下は、反転の原則への違法な依存の例です。私達には高レベルのマネージャーと低レベルの労働者がいます。新しいモジュールをアプリケーションに追加するという形で、新しい特別な労働者が会社の構造に与える影響をシミュレートします。このための新しいSuperWorkerクラスを作成します。
Managerクラスが非常に複雑で、非常に複雑なロジックが含まれていると仮定します。そして今、私たちは新しいスーパーワーカーを導入するためにそれを変更する必要があります。欠点を見てみましょう:
- Managerクラスを変更する必要があります(これは複雑なクラスであり、変更には時間と労力がかかることを忘れないでください)。
- マネージャークラスの現在の機能の一部が影響を受ける可能性があります。
- 単体テストをやり直す必要があります。
Managerクラスは非常に複雑で、複雑なロジックが含まれていると想定します。ここで、新しいSuperWorkerクラスを導入するために変更する必要がありますが、これには次の欠点があります。
- Managerクラスを変更する必要があります(このクラスは複雑で、変更に対応するには時間と労力が必要です)。
- マネージャークラスの一部の現在の機能が影響を受ける可能性があります。
- 単体テストを繰り返す必要があります。
これらの問題はすべて解決に時間がかかり、古い機能に新しいエラーが発生する可能性があります。アプリケーションが依存関係の逆転の原則に従って設計されていた場合、状況は異なります。つまり、マネージャークラス、IWorkerインターフェイス、およびIWorkerインターフェイスを実装するWorkerクラスを設計します。SuperWorkerクラスを追加する必要がある場合、IWorkerインターフェイスを実装するだけです。既存のクラスに追加の変更はありません。
これらの問題の解決には時間がかかり、元の関数に新しいエラーが発生する可能性があります。アプリケーションが依存関係の逆転の原理を使用して設計されている場合、上記の状況は発生しません。原則に従って、マネージャークラス、IWorkerインターフェイス、およびIWorkerインターフェイスを実装するWorkerクラスを設計する必要があります。新しいSuperWorkerクラスを追加する必要がある場合は、そのためのIWorkerインターフェイスを実装するだけでよく、既存のクラスに追加の変更は行われません。
1 // 依存関係の逆転の原則-悪い例 2 3 クラスワーカー{ 4 5 public void work(){ 6 7 // .... working 8 9 } 10 11 } 12 13 14 15 クラスマネージャー{ 16 17 ワーカーワーカー; 18 19 20 21 public void setWorker(Worker w){ 22 worker = w; 23 } 24 25 public void manage(){ 26 worker.work(); 27 } 28 } 29 30 クラスSuperWorker { 31 public void work(){ 32 // ....より多くの作業 33 } 34 }
以下は、依存関係逆転原理をサポートするコードです。この新しいデザインでは、IWorkerインターフェイスを介して新しい抽象化レイヤーが追加されます。これで、上記のコードの問題が解決されました(高レベルのロジックに変更がないことを考慮して)。
- SuperWorkersを追加するとき、Managerクラスは変更を必要としません。
- 変更しないため、Managerクラスに存在する古い機能に影響を与えるリスクを最小限に抑えます。
- Managerクラスの単体テストをやり直す必要はありません。
次のコードは、依存関係リードの原則をサポートしています。この新しいデザインでは、IWorkerインターフェイスを介して新しい抽象化レイヤーが追加されます。上記のコードの問題は解決されました(高レベルのロジックではコピーは変更されていません)。
- SuperWorkersを追加するときにManagerクラスを変更する必要はありません。
- Managerクラスは変更しないため、元の関数に影響が及ぶリスクは最小限に抑えられます。
- Managerクラスを再ユニットテストする必要はありません。
1 // 依存関係の逆転の原則-良い例 2 インターフェースIWorker { 3 public void work(); 4 } 5 6 クラス Worker はIWorkerを実装します{ 7 public void work(){ 8 // ..... working 9 } 10 } 11 12 クラス SuperWorker はIWorker { 13 public void work(){ 14 // .... workingを 実装しますはるかに15 } 16 } 17 18 クラスマネージャ{ 19 IWorkerワーカー。 20 21 public void setWorker(IWorker w){ 22 worker = w; 23 } 24 25 public void manage(){ 26 worker.work(); 27 } 28 }
結論
おわりに
この原則が適用されると、高レベルのクラスが低レベルのクラスと直接連携するのではなく、抽象レイヤーとしてインターフェイスを使用することになります。この場合、(必要に応じて)高レベルクラス内の新しい低レベルオブジェクトのインスタンス化は、演算子newを使用して実行できません。代わりに、ファクトリーメソッド、抽象ファクトリー、プロトタイプなどの一部の作成デザインパターンを使用できます。
この原則が適用される場合、高レベルのクラスは低レベルのクラスの作業に直接依存せず、抽象レイヤーインターフェイスを使用することを意味します。この場合、演算子newを使用して、上位クラスの下位クラスインスタンスを割り当てることはできません。作成デザインパターンを使用して、ファクトリメソッド、抽象ファクトリ、プロトタイプパターンなどのサポートを提供できます。
テンプレートデザインパターンは、DIPの原則が適用される例です。
テンプレート設計パターンは、反転の原理の適用例です。
もちろん、この原則を使用すると、労力が増えることを意味し、維持するクラスとインターフェースが増えます。つまり、より複雑なコードでは、より柔軟になります。この原則は、すべてのクラスまたはすべてのモジュールに盲目的に適用されるべきではありません。将来変更されない可能性が高いクラス機能がある場合、この原則を適用する必要はありません。
もちろん、この原則を使用すると、維持する必要のあるクラスとインターフェースが増えるため、より多くのエネルギーを費やす必要があります。つまり、この原則は、より柔軟な設計と引き換えに、より複雑なコードを使用します。このパターンは、クラスやモジュールで無差別に使用するべきではなく、クラス関数を将来変更する必要がない場合は、この原則を使用する必要はありません。
元のアドレス:https : //www.oodesign.com/dependency-inversion-principle.html