web-dev-qa-db-ja.com

すべてのクラスのインターフェースを抽出することがベストプラクティスですか?

すべてのクラスが実装するインターフェースを持つコードを見てきました。

それらすべてに共通のインターフェースがない場合もあります。

それらはそこにあり、具体的なオブジェクトの代わりに使用されます。

これらは2つのクラスの汎用インターフェイスを提供せず、クラスが解決する問題の領域に固有です。

それをする理由はありますか?

51
the_drow

この回答を再検討した後、少し修正することにしました。

いいえ、everyクラスのインターフェースを抽出することはベストプラクティスではありません。これは実際には逆効果になる可能性があります。ただし、インターフェイスはいくつかの理由で役立ちます。

  • テストサポート(モック、スタブ)。
  • 実装の抽象化(IoC/DIへの拡張)。
  • C#での共分散や逆分散のサポートなどの付随的なもの。

これらの目標を達成するために、インターフェースは考慮される良い習慣です(そして、実際には最後のポイントに必要です)。プロジェクトのサイズによっては、インターフェースと対話する必要がない場合や、上記のいずれかの理由でインターフェースを常に抽出している場合があります。

私たちは大きなアプリケーションを維持しており、その一部は素晴らしいものであり、一部は注意力の欠如に苦しんでいます。インターフェースを型から取り出してテスト可能にするためにリファクタリングすることがよくあります。そうすることで、変更の影響を軽減しながら実装を変更できます。また、これは、パブリックAPIに厳密でない場合に具象型が誤って課す可能性のある「カップ​​リング」の影響を軽減するためにも行われます(インターフェースはパブリックAPIのみを表すことができるため、本質的に非常に厳密になります)。

とはいえ、インターフェースなしで動作を抽象化したり、インターフェースを必要とせずに型をテストしたりすることは可能であるため、これらは上記の要件ではありません。これらのタスクをサポートするために使用できるほとんどのフレームワーク/ライブラリは、インターフェイスに対して効果的に機能するだけです。


私は古い答えを文脈に残しておきます。

インターフェイスは、パブリックコントラクトを定義します。インターフェイスを実装する人々は、この契約を実装する必要があります。消費者は公的な契約しか見ることができません。これは、実装の詳細が消費者から抽出されたことを意味します。

最近このためにすぐに使用されるのはUnit Testingです。インターフェイスのモック、スタブ、フェイクは簡単です。

別の即時使用はDependency Injectionです。特定のインターフェースに登録された具象型は、インターフェースを消費する型に提供されます。タイプは実装を特に気にしませんので、抽象的にインターフェースを要求できます。これにより、多くのコードに影響を与えることなく実装を変更できます(契約が同じである限り、影響範囲は非常に小さくなります)。

非常に小さなプロジェクトの場合は気にしない傾向があり、中規模のプロジェクトの場合は重要なコアアイテムに悩む傾向があります。また、大規模なプロジェクトの場合は、ほぼすべてのクラスにインターフェイスがある傾向があります。これはほとんど常にテストをサポートするためですが、注入された動作の一部のケース、またはコードの重複を減らすための動作の抽象化です。

28

番号。

インターフェースは、複雑な動作をするクラスに適しています。ユニットテストで使用するために、そのインターフェースのモックまたは偽の実装クラスを作成できるようにする場合は、特に便利です。

ただし、一部のクラスは動作が多くなく、値のように扱うことができ、通常は一連のデータフィールドで構成されます。このようなクラスのインターフェースを作成しても、インターフェースのモック化や代替実装を提供する意味がほとんどない場合、不要なオーバーヘッドが発生するため、ほとんど意味がありません。たとえば、次のクラスを考えてみます。

class Coordinate
{
  public Coordinate( int x, int y);
  public int X { get; }
  public int y { get; }
}

ICoordinateXの値を単に取得して設定する以外の方法で実装する意味がないため、このクラスで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 );
}

ルートの計画に使用できる多くの異なるアルゴリズムがあるため、IRoutePlannerRoutePlannerインターフェースがおそらく必要になります。

また、3番目のクラスがある場合:

class RobotTank
{
   public RobotTank( IRoutePlanner );
   public void DriveRoute( List<Coordinate> points );
}

RoutePlannerにインターフェースを与えることにより、RobotTankのテストメソッドを作成して、モックRoutePlannerを使用して特定の順序で座標のリストを返すだけのテストメソッドを作成できます。これにより、テストメソッドは、ルートプランナーをテストすることなく、タンクが座標間を正しく移動することを確認できます。つまり、ルートプランナーをテストすることなく、1つのユニット(戦車)をテストするだけのテストを作成できます。

ただし、実際のCoordinateをICoordinateインターフェイスの背後に隠す必要なく、このようなテストにフィードするのは非常に簡単です。

41
Scott Langham

OO達人、マーティンファウラー、引用してみましょう。このスレッドで最も一般的な答えに確かな根拠を加えてください。

この抜粋は、「エンタープライズアプリケーションアーキテクチャのパターン」(「プログラミングの古典」および「開発者は必ず読む必要がある」本に記載されている)から引用しています。カテゴリー)。

[パターン] 分離インターフェイス

(...)

いつ使用するか

システムの2つの部分間の依存関係を解消する必要がある場合は、分離インターフェースを使用します。

(...)

作成するクラスごとに個別のインターフェースを持っている多くの開発者に出会います。特にアプリケーション開発にとって、これは過剰だと思います。個別のインターフェースと実装を維持することは、特にファクトリクラス(インターフェースと同様に、依存関係を壊したい場合、または複数の独立した実装が必要な場合にのみ、別のインターフェースを使用することをお勧めします。インターフェースと実装を組み合わせて後で分離する必要がある場合、これはあなたがそれをする必要があるまで遅延することができる単純なリファクタリング。

あなたの質問に答える:いいえ

私はこのタイプの「派手な」コードのいくつかを私自身が見たことがあります。開発者は彼を無垢だと思っていますが、その代わりに理解できず、拡張が難しく、複雑すぎます。

8
andrew.fox

プロジェクトの各クラスのインターフェイスを抽出する背後に実用的な理由はありません。それはやり過ぎでしょう。インターフェースを抽出する必要があるのは、OOADの原則「 Program to Interface、not Implementation 」を実装しているように見えるためです。この原則の詳細については、例 こちら を参照してください。

インターフェースとインターフェースへのコーディングにより、実装の交換が非常に簡単になります。これは単体テストにも当てはまります。インターフェースを使用するコードをテストする場合は、(理論的には)具象オブジェクトの代わりにモックオブジェクトを使用できます。これにより、テストの焦点を絞って、より細かくすることができます。

私が見てきたことから、テスト(モック)のために実装を切り替えてから実際の本番コードで切り替えることがより一般的です。そして、はい、それはユニットテストのためにそれを怒らせます。

4
Tony

すべてのクラスにとってそれが妥当だとは思いません。

それは、どのタイプのコンポーネントからどの程度の再利用を期待できるかという問題です。もちろん、現時点で実際に使用するよりも多くの再利用を計画する必要があります(後で大規模なリファクタリングを行う必要はありません)。ただし、プログラム内のすべてのクラスの抽象インターフェイスを抽出すると、クラスが少なくなります。必要。

2

ばかげているように見えるかもしれませんが、この方法でこれを行うことの潜在的な利点は、ある時点で特定の機能を実装するより良い方法があることに気づいた場合、同じインターフェイスを実装する新しいクラスを作成し、1行をすべてのコードでそのクラスを使用するようにします。インターフェース変数が割り当てられている行です。

この方法で行う(同じインターフェイスを実装する新しいクラスを作成する)と、古い実装と新しい実装を常に切り替えて比較することもできます。

最終的には、この便利さを利用することができず、最終的な製品は、各インターフェース用に作成された元のクラスを実際に使用するだけです。その場合は、すばらしいです!しかし、これらのインターフェースを作成するのにそれほど時間はかかりませんでした。必要な場合は、時間を大幅に節約できました。

2
Dan Tao

私はcouldが時間または空間の2つの異なる方法で実装されるもの、つまり将来的に別の方法で実装されるか、コードの異なる部分に2つの異なるコードクライアントがあるもののインターフェイスが好きです別の実装が必要な場合があります。

コードの元々の作成者はロボコーディングだったかもしれません。あるいは、彼らは賢く、バージョンの復元力を準備しているか、ユニットテストの準備をしていました。前者の可能性が高いのは、バージョンの復元が一般的ではないためです(つまり、クライアントがデプロイされていて変更できない場合、既存のクライアントと互換性のあるコンポーネントをデプロイする必要があります)。

私はテストする予定の他のいくつかのコードから分離する価値がある依存関係であるもののインターフェースが好きです。これらのインターフェースが単体テストをサポートするために作成されていない場合、私はそれらがそのような良いアイデアであるとは確信していません。インターフェースは維持するコストがあり、オブジェクトを別のものと交換可能にするときは、インターフェースをいくつかのメソッドのみに適用したいかもしれません(そのため、より多くのクラスがインターフェースを実装できます)。抽象を使用する方が良いかもしれませんクラス(デフォルトの動作を継承ツリーに実装できるようにするため)。

したがって、事前に必要なインターフェースはおそらく良い考えではありません。

2
MatthewMartin

(ユニット)テストの際にクラスを模擬できるため、インターフェースは便利です。

私は、少なくとも外部リソース(データベース、ファイルシステム、Webサービスなど)にアクセスするすべてのクラスのインターフェイスを作成し、モックを作成するか、モックフレームワークを使用して動作をシミュレートします。

1
adrianm

Dependency Inversion の原則の一部である場合。基本的にコードは実装ではなくインターフェースに依存します。

これにより、呼び出しクラスに影響を与えることなく、実装を簡単に入れ替えることができます。それは、システムのメンテナンスをはるかに簡単にする疎結合を可能にします。

システムが成長して複雑になるにつれて、この原則はますます理にかなっています!

1
Mongus Pong

将来、他の実装を確実に注入できるようにしたい場合があります。一部の(多分ほとんどの)場合、これはやり過ぎですが、ほとんどの習慣と同じです-慣れていれば、それを実行するのにそれほど時間はかかりません。そして、確信が持てないwhat将来置換したいので、すべてのクラスでインターフェースを抽出することには意味があります。

問題の解決策は1つだけではありません。したがって、同じインターフェースの複数の実装が常に存在する可能性があります。

1
Tomas Aschan

なぜインターフェースが必要なのですか?実践的かつ深く考えます。インターフェイスは実際にはクラスにアタッチされておらず、サービスにアタッチされています。インターフェイスの目標は、他の人がコードを提供せずにコードを使用できるようにすることです。したがって、それはサービスとその管理に関連しています。

じゃあ

0
Muktadir

インターフェイスは動作を定義します。 1つ以上のインターフェースを実装する場合、オブジェクトは1つまたは他のインターフェースが説明するように動作します。これにより、クラス間の疎結合が可能になります。これは、実装を別の実装に置き換える必要がある場合に非常に役立ちます。クラス間の通信は、クラスが相互に本当に緊密にバインドされている場合を除いて、常にインターフェースを使用して行われます。

0
jdehaan