私は、すべてのクラスがそれが実装するインターフェースを持っているコードを見てきました。
それらすべてに共通のインターフェースがない場合もあります。
それらはただそこにあり、具体的なオブジェクトの代わりに使用されます。
これらは2つのクラスの汎用インターフェースを提供せず、クラスが解決する問題のドメインに固有です。
それをする理由はありますか?
私は、すべてのクラスがそれが実装するインターフェースを持っているコードを見てきました。
それらすべてに共通のインターフェースがない場合もあります。
それらはただそこにあり、具体的なオブジェクトの代わりに使用されます。
これらは2つのクラスの汎用インターフェースを提供せず、クラスが解決する問題のドメインに固有です。
それをする理由はありますか?
いいえ。
インターフェイスは、複雑な動作をするクラスに適しており、単体テストで使用するインターフェイスのモックまたは偽の実装クラスを作成できるようにする場合に特に便利です。
ただし、一部のクラスは多くの動作を持たず、値のように扱うことができ、通常は一連のデータ フィールドで構成されます。このようなクラスのインターフェイスを作成する意味はほとんどありません。インターフェイスの代替実装をモックしたり提供したりする意味がほとんどない場合、そうすると不要なオーバーヘッドが発生するからです。たとえば、クラスを考えてみましょう:
class Coordinate
{
public Coordinate( int x, int y);
public int X { get; }
public int y { get; }
}
単純に値ICoordinate
を取得して設定する以外の方法で実装する意味はほとんどないため、このクラスにインターフェイスを使用することはほとんどありません。X
Y
ただし、クラス
class RoutePlanner
{
// Return a new list of coordinates ordered to be the shortest route that
// can be taken through all of the passed in coordinates.
public List<Coordinate> GetShortestRoute( List<Coordinate> waypoints );
}
ルートの計画に使用できるさまざまなアルゴリズムが多数あるため、おそらくIRoutePlanner
インターフェイスが必要になるでしょう。RoutePlanner
また、3 番目のクラスがある場合:
class RobotTank
{
public RobotTank( IRoutePlanner );
public void DriveRoute( List<Coordinate> points );
}
インターフェイスを与えることで、特定の順序で座標のリストを返すだけのモックを作成RoutePlanner
するテスト メソッドを作成できます。これにより、テスト メソッドは、ルート プランナーをテストすることなく、戦車が座標間を正しくナビゲートすることを確認できます。これは、ルート プランナーをテストせずに、1 つのユニット (タンク) だけをテストするテストを作成できることを意味します。RobotTank
RoutePlanner
ICoordinate
ただし、インターフェイスの背後に隠すことなく、このようなテストに実際の座標をフィードするのは非常に簡単です。
この回答を再検討した後、少し修正することにしました。
いいえ、すべてのクラスのインターフェイスを抽出するのはベスト プラクティスではありません。これは実際には逆効果になる可能性があります。ただし、インターフェースはいくつかの理由で役立ちます。
これらの目標を達成するために、インターフェイスは優れた方法と見なされます(そして、最後の点で実際に必要です)。プロジェクトのサイズによっては、インターフェイスと対話する必要がない場合や、上記の理由のいずれかで常にインターフェイスを抽出している場合があります。
私たちは大規模なアプリケーションを維持しています。その一部は素晴らしいものであり、一部は注意の欠如に苦しんでいます。インターフェースを型から取り出してテスト可能にするため、またはその変更の影響を軽減しながら実装を変更できるようにするために、リファクタリングを行うことがよくあります。これは、パブリック API に厳密でない場合に具象型が誤って課す可能性のある「カップリング」効果を減らすためにも行います (インターフェイスはパブリック API のみを表すことができるため、本質的に非常に厳密になります)。
とはいえ、インターフェイスなしで動作を抽象化することは可能であり、インターフェイスを必要とせずに型をテストすることは可能であるため、上記の要件ではありません。これらのタスクをサポートするために使用できるほとんどのフレームワーク/ライブラリが、インターフェイスに対して効果的に動作するというだけです。
インターフェイスは、パブリック コントラクトを定義します。インターフェイスを実装する人は、このコントラクトを実装する必要があります。コンシューマは、パブリック コントラクトのみを表示します。これは、実装の詳細が消費者から離れて抽象化されたことを意味します。
最近これをすぐに使用するのはUnit Testingです。インターフェイスは、モック、スタブ、フェイクなど、簡単に作成できます。
もう 1 つのすぐに使用できるのはDependency Injectionです。特定のインターフェイスの登録された具象型は、インターフェイスを使用する型に提供されます。型は実装を特に気にしないため、抽象的にインターフェイスを要求できます。これにより、多くのコードに影響を与えることなく実装を変更できます (コントラクトが同じままである限り、影響範囲は非常に小さくなります)。
非常に小規模なプロジェクトの場合は気にしない傾向があり、中規模のプロジェクトの場合は重要なコア項目を気にする傾向があり、大規模なプロジェクトの場合は、ほぼすべてのクラスのインターフェイスが存在する傾向があります。これはほとんどの場合、テストをサポートするためですが、場合によっては、動作が挿入されたり、コードの重複を減らすために動作が抽象化されたりします。
OO の第一人者である Martin Fowler の言葉を引用して、このスレッドで最も一般的な回答に確固たる理由を追加します。
この抜粋は、「エンタープライズ アプリケーション アーキテクチャのパターン」 (「プログラミングの古典」および\または「すべての開発者が読む必要がある」本のカテゴリに登録されている) からのものです。
【パターン】インターフェース分離
(...)
いつ使用するか
システムの 2 つの部分間の依存関係を解消する必要がある場合は、分離インターフェイスを使用します。
(...)
作成するクラスごとに個別のインターフェースを持つ多くの開発者に出くわします。特にアプリケーション開発では、これは過剰だと思います。特にファクトリ クラス (インターフェイスと実装を含む) も必要になることが多いため、インターフェイスと実装を別々に保持することは余分な作業です。アプリケーションについては、依存関係を解消したい場合、または複数の独立した実装を持ちたい場合にのみ、個別のインターフェースを使用することをお勧めします。インターフェイスと実装をまとめて、後で分離する必要がある場合、これは単純なリファクタリングであり、必要になるまで遅らせることができます。
質問への回答: いいえ
私自身、このタイプの「ファンシーな」コードをいくつか見たことがあります。開発者は自分が SOLID だと思っているのに、理解できず、拡張が難しく、複雑すぎます。
プロジェクト内の各クラスのインターフェイスを抽出する実際的な理由はありません。それはやり過ぎでしょう。インターフェイスを抽出しなければならない理由は、OOAD の原則「実装ではなく、インターフェイスへのプログラム」を実装しているように見えるという事実です。この原則の詳細については、こちらの例を参照してください。
インターフェイスとインターフェイスへのコーディングがあると、実装の交換が非常に簡単になります。これは単体テストにも当てはまります。インターフェイスを使用するコードをテストする場合、(理論的には) 具象オブジェクトの代わりにモック オブジェクトを使用できます。これにより、テストをより焦点を絞ってきめ細かくすることができます。
私が見てきたことから、実際の製品コードではなく、テスト (モック) の実装を切り替えることがより一般的です。そして、はい、単体テストに値します。
私は、時間または空間のいずれかで 2 つの異なる方法で実装できるもののインターフェイスが好きです。つまり、将来別の方法で実装される可能性があるか、別の実装が必要なコードの異なる部分に 2 つの異なるコード クライアントが存在するかのいずれかです。
コードの最初の作成者はロボ コーディングだった可能性があります。または、賢くてバージョンの回復力や単体テストの準備をしていた可能性があります。バージョンの回復力は一般的ではないため、前者の可能性が高くなります (つまり、クライアントが展開され、変更できず、既存のクライアントと互換性がなければならないコンポーネントが展開される場合)。
私は、テストする予定の他のコードから分離する価値のある依存関係のインターフェイスが好きです。これらのインターフェイスが単体テストをサポートするために作成されていない場合、それらがそれほど良いアイデアであるかどうかはわかりません。インターフェイスには維持するコストがかかり、オブジェクトを別のオブジェクトと交換可能にするときが来たら、インターフェイスを少数のメソッドにのみ適用したい場合があります (より多くのクラスがインターフェイスを実装できるようにするため)。クラス (デフォルトの動作を継承ツリーに実装できるようにするため)。
したがって、事前に必要なインターフェースはおそらく良い考えではありません。
すべてのクラスにとって合理的ではないと思います。
どのタイプのコンポーネントからどれだけの再利用を期待するかは問題です。もちろん、現時点で実際に使用するよりも多くの再利用を (後で大規模なリファクタリングを行う必要なく) 計画する必要がありますが、プログラム内のすべての単一クラスの抽象インターフェイスを抽出すると、必要です。
If は依存性逆転の原則の一部です。基本的に、コードは実装ではなくインターフェイスに依存します。
これにより、呼び出し元のクラスに影響を与えることなく、実装を簡単に入れ替えることができます。これにより、より緩い結合が可能になり、システムのメンテナンスがはるかに簡単になります。
システムが成長し、より複雑になるにつれて、この原則はますます理にかなっています!
将来、他の実装を確実に注入できるようにしたい場合は、あるかもしれません。いくつかの (おそらくほとんどの) ケースでは、これはやり過ぎですが、ほとんどの習慣と同様です。また、将来何を置き換えたいかを確信することはできないため、すべてのクラスでインターフェイスを抽出することには意味があります。
問題の解決策は決して 1 つではありません。したがって、同じインターフェースの複数の実装が常に存在する可能性があります。
ばかげているように思えるかもしれませんが、このようにすることの潜在的な利点は、ある時点で特定の機能を実装するためのより良い方法があることに気付いた場合、同じインターフェイスを実装する新しいクラスを作成し、1 行を次のように変更するだけでよいことです。すべてのコードでそのクラスを使用するようにします。インターフェイス変数が割り当てられている行です。
このようにすること (同じインターフェースを実装する新しいクラスを作成すること) は、古い実装と新しい実装をいつでも切り替えて比較できることも意味します。
この便利さをまったく利用せず、最終製品は実際には各インターフェース用に作成された元のクラスを使用するだけになる可能性があります。もしそうなら、素晴らしいです!しかし、これらのインターフェースを作成するのに実際にはそれほど時間はかかりませんでした。また、それらが必要であれば、多くの時間を節約できたはずです。
(単体)テスト時にクラスをモックできるため、インターフェイスはあると便利です。
外部リソース (データベース、ファイルシステム、Web サービスなど) に触れる少なくともすべてのクラスのインターフェイスを作成し、モックを作成するか、モック フレームワークを使用して動作をシミュレートします。
インターフェイスは動作を定義します。1 つまたは複数のインターフェイスを実装すると、オブジェクトは 1 つまたは他のインターフェイスが説明するように動作します。これにより、クラス間の疎結合が可能になります。実装を別のものに置き換える必要がある場合に非常に便利です。クラス間の通信は、クラスが相互に非常に緊密にバインドされている場合を除いて、常にインターフェイスを使用して行われます。
なぜインターフェースが必要なのですか?実際的かつ深く考える。インターフェイスは実際にはクラスに関連付けられているのではなく、サービスに関連付けられています。インターフェースの目標は、コードを提供することなく、他の人があなたのコードで何をできるようにするかです。したがって、サービスとその管理に関連しています。
じゃあ