交換可能かつテスト可能であるためには、通常、ロジックを備えたサービスにはインターフェースが必要です。
public class FooService: IFooService
{ ... }
デザイン的にはこれに同意しますが、このアプローチで気になるのは、1つのサービスで2つのこと(クラスとインターフェイス)を宣言する必要があり、私たちのチームでは通常2つのファイル(1つクラスとインターフェースの1つ)。 IDE(VS2010)で[定義に移動]を使用すると、実際のクラスではなく、インターフェースを指すため、他のクラスはインターフェースを指すため、ナビゲーションの難しさも不快です。
IFooServiceをFooServiceと同じファイルに書き込むと、上記の奇妙さが軽減されると考えていました。結局のところ、IFooServiceとFooServiceは非常に関連しています。これは良い習慣ですか? IFooServiceを独自のファイルに配置する必要があるという正当な理由はありますか?
独自のファイルにある必要はありませんが、チームは標準を決定し、それに固執する必要があります。
また、「定義に移動」でインターフェースに移動することは正しいですが、 Resharper がインストールされている場合、クリックするだけで、そのインターフェースから派生クラス/インターフェースのリストが開きます。たいしたことじゃない。そのため、インターフェースを別のファイルに保存しています。
別々のファイルにしておくべきだと思います。あなたが言ったように、アイデアはテスト可能で互換性を維持することです。インターフェースを実装と同じファイルに配置することで、インターフェースを特定の実装に関連付けます。モックオブジェクトまたは別の実装を作成する場合、インターフェイスはFooServiceから論理的に分離されません。
SOLIDによると、インターフェースを作成するだけでなく、それを別のファイルに含めるだけでなく、別のアセンブリにも含める必要があります。
どうして?アセンブリにコンパイルされるソースファイルへの変更にはアセンブリの再コンパイルが必要であり、アセンブリへの変更には依存するアセンブリの再コンパイルが必要だからです。したがって、SOLIDに基づく目的が実装Aを実装Bで置き換えることができる場合、クラスCはインターフェイスに依存しているため、違いを知る必要はありませんが、アセンブリをIで確認する必要があります。変化しないため、使用法が保護されます。
「しかし、それはただの再コンパイルだ」私はあなたが抗議するのを聞いています。そうかもしれませんが、スマートフォンアプリでは、ユーザーのデータ帯域幅の方が簡単です。変更された1つのバイナリをダウンロードするのか、それともそれに依存するコードでそのバイナリと5つの他のバイナリをダウンロードするのか?すべてのプログラムがLAN上のデスクトップコンピューターで使用されるように作成されているわけではありません。その場合でも、帯域幅とメモリが安価な場合、パッチのリリースが小さいほど、Active Directoryまたは同様のドメイン管理レイヤーを介してLAN全体にプッシュアウトするのは簡単であるため、価値があります。ユーザーは、すべてが再インストールされるのに数分かかるのではなく、次回のログイン時にそれが適用されるのを数秒だけ待ちます。言うまでもなく、プロジェクトをビルドするときに再コンパイルする必要があるアセンブリが少ないほど、ビルドが高速になり、変更ごとに50のアセンブリがビルドされるのを待つ時間を節約できるため、生産性が向上します。
さて、免責事項:これは常に可能であるか、実現可能であるとは限りません。これを行う最も簡単な方法は、一元化された「インターフェース」プロジェクトを作成することです。これには独自の欠点があります。アプリの永続化レイヤーまたは他の主要コンポーネントを再利用する他のアプリでインターフェースプロジェクトと実装プロジェクトを参照する必要があるため、コードの再利用性が低下します。インターフェイスをより密に結合されたアセンブリに分割することでこの問題を克服できますが、アプリにプロジェクトが多くなり、フルビルドが非常に面倒になります。重要なのは、バランスと、疎結合設計を維持することです。通常、必要に応じてファイルを移動できます。そのため、クラスに多くの変更が必要な場合、またはインターフェースの新しい実装が定期的に必要になる場合があります(おそらく、他のソフトウェアの新しいサポートバージョンやファイルタイプとインターフェースするためなど)。 )インターフェースから切り離して、それらの変更に関する知識から使用法を保護できます。
個別のファイルを使用することが不快なのはなぜですか?私にとっては、それはずっとすっきりとしていてきれいです。ソリューションエクスプローラーに表示するファイルの数を減らしたい場合は、 "Interfaces"という名前のサブフォルダーを作成し、そこにIFooServer.csファイルを貼り付けるのが一般的です。
インターフェイスが独自のファイルで定義される理由は、通常、クラスが独自のファイルで定義されるのと同じ理由です。論理構造とファイル構造が同一の場合、プロジェクト管理はより簡単になるため、特定のクラスが定義されているファイルを常に把握できます。これにより、デバッグ時(例外スタックトレースでは通常、ファイルと行番号が表示されます)またはソース管理リポジトリのソースコードをマージするときに、作業が楽になります。
多くの場合、コードファイルに単一のクラスまたは単一のインターフェイスのみを含めることをお勧めします。しかし、これらのコーディングプラクティスは、目的を達成するための手段です。コードをより適切に構造化して、作業しやすくするためです。あなたとあなたのチームが、クラスがそれらのインターフェースと一緒に維持されていると、作業しやすくなるので、ぜひそうしてください。
個人的には、あなたの場合のように、インターフェースを実装するクラスが1つしかない場合は、インターフェースとクラスを同じファイルに置くことを好みます。
ナビゲーションの問題に関しては、 ReSharper を強くお勧めします。特定のインターフェイスメソッドを実装するメソッドに直接ジャンプするための非常に便利なショートカットがいくつか含まれています。
インターフェイスをクラスとの個別のファイルだけでなく、完全に別のアセンブリ。
たとえば、ワイヤーの両端を制御できる場合、クライアントとサービスの両方でWCFサービスコントラクトインターフェイスを 共有 にすることができます。インターフェイスを独自のアセンブリに移動すると、独自のアセンブリの依存関係が少なくなります。これにより、クライアントの消費がはるかに容易になり、その実装との結合が緩くなります。
Robert C. MartinによるAgile Principles、Practices and Patternsで定義されているように、インターフェイスはクライアントではなく実装に属しています。したがって、インターフェースと実装を同じ場所で組み合わせることは、その原則に反します。
クライアントコードはインターフェースに依存します。これにより、クライアントコードとインターフェイスwithout実装をコンパイルおよびデプロイすることができます。クライアントコードに対してpluginsのように動作するさまざまな実装を持つことができます。
更新:これは、アジャイルのみの原則ではありません。 '94年に遡るデザインパターンのギャングオブフォーは、インターフェイスに固執するクライアントとインターフェイスへのプログラミングについて既に話し合っています。彼らの見解は似ています。
インターフェースの単一の実装を持つことは、たとえあったとしても、ほとんど意味がありません1。パブリックインターフェイスとそのインターフェイスを実装するパブリッククラスを同じファイルに配置すると、インターフェイスが不要になる可能性が高くなります。
インターフェイスと同じ場所に配置するクラスがabstractであり、インターフェイスのすべての実装がその抽象クラスを継承する必要があることがわかっている場合、同じファイルで2つを配置することは意味があります。インターフェースを使用するという決定を精査する必要があります。抽象クラス自体で問題ないかどうかを自問し、答えが肯定的である場合はインターフェースを削除してください。
ただし、一般的には、「1つのパブリッククラス/インターフェイスが1つのファイルに対応する」戦略に従う必要があります。従うのは簡単で、ソースツリーのナビゲートが容易になります。
クラスとインターフェースを同じファイルに配置しても、どちらを使用しても制限はありません。インターフェイスは、それを実装するクラスと同じファイル内にある場合でも、モックなどに同じ方法で使用できます。この質問は、組織の利便性の1つとして純粋に認識される可能性があります。
もちろん、2つを同じファイルに含めると、不注意にそれらが実際よりもプログラムで結合されていると考えるようになる可能性があり、これはリスクです。
個人的には、一方の規約をもう一方の規約で使用しているコードベースに遭遇した場合、どちらの方法でもそれほど心配する必要はありません。
個人的には、インターフェースの前の「私」は好きではありません。そのようなタイプのユーザーとして、これがインターフェースであるかどうかを見たくありません。タイプは面白いものです。依存関係に関しては、FooServiceはIFooServiceの可能な実装の1つです。インターフェイスは、使用される場所の近くにある必要があります。一方、実装は、クライアントに影響を与えずに簡単に変更できる場所にある必要があります。したがって、通常は2つのファイルを使用します。
インターフェースへのコーディングは悪い場合があり、実際には特定のコンテキストのアンチパターンとして扱われ、法律ではありません。通常、インターフェースのアンチパターンへのコーディングの良い例は、アプリケーションに実装が1つしかないインターフェースがある場合です。もう1つは、インターフェースファイルが煩雑になり、実装されたクラスのファイルでその宣言を非表示にする必要がある場合です。