web-dev-qa-db-ja.com

1つのクラスのみが実装するインターフェースを使用する必要がありますか?

複数のクラスが一連の規則と実装に準拠しているというインターフェースの要点ではありませんか?

255
Lamin Sanneh

厳密に言えば、あなたはしません [〜#〜] yagni [〜#〜] が適用されます。そうは言っても、インターフェイスの作成に費やす時間は最小限です。特に、ほとんどの作業を行う便利なコード生成ツールがある場合はそうです。のインターフェイスが必要かどうかわからない場合は、インターフェイスの定義をサポートする方向に誤りがある方がいいと思います。

さらに、単一のクラスでもインターフェイスを使用すると、ユニットテスト用に別のモック実装が提供されます。 Avner Shahar-Kashtanの答え はこの点を拡張しています。

209
yannis

あなたがインターフェースを必要とするかどうかはnotがそれを実装するクラスの数に依存すると私は答えます。インターフェイスは、アプリケーションの複数のサブシステム間でcontractsを定義するためのツールです。したがって、本当に重要なのは、アプリケーションがサブシステムにどのように分割されるかです。カプセル化されたサブシステムのフロントエンドとして、多くのクラスがそれらを実装するインターフェースに関係なく、インターフェースが必要です。

以下に、非常に役立つ経験則を示します。

  • クラスFooがクラスBarImplを直接参照するようにした場合、Fooを変更するたびにBarImplを変更することを強くコミットしていることになります。基本的には、それらを2つのクラスに分割された1つのコード単位として扱います。
  • FooインターフェースBarを参照させると、代わりにavoidは、Fooを変更するとBarImplに変更されます。

アプリケーションの重要なポイントでインターフェイスを定義する場合、それらがサポートする必要があるメソッドとサポートしないメソッドを慎重に検討し、実装がどのように動作するか(およびどのように動作しないか)を説明するためにインターフェースに明確にコメントします。これらのコメント付きインターフェースは、アプリケーションの一種の仕様を提供するため、アプリケーションは非常に理解しやすくなります。その方法の説明意図したの動作。これにより、コードの読み取りがはるかに簡単になります(「このコードが何を実行するのか」と尋ねるのではなく、「このコードは想定されていることをどのように実行するのか」と尋ねることができます)。

これらすべてに加えて(または実際にはそれが原因で)、インターフェースは個別のコンパイルを促進します。インターフェースはコンパイルが簡単であり、実装よりも依存関係が少ないため、クラスFooを作成してインターフェースBarを使用する場合、通常はBarImplを再コンパイルする必要はありません。 Fooを再コンパイルします。大規模なアプリケーションでは、これにより多くの時間を節約できます。

150
sacundim

インターフェースは、動作、つまり関数/メソッドのプロトタイプのセットを定義するように指定されています。インターフェースを実装するタイプはその動作を実装するため、そのようなタイプを扱うとき、その動作が(部分的に)わかっています。

定義された動作が1回だけ使用されることがわかっている場合は、インターフェースを定義する必要はありません。 [〜#〜] kiss [〜#〜](シンプルに、バカにしてください)

102
superM

理論的には、インターフェースを持つためだけにインターフェースを用意するべきではありませんが、 Yannis Rizosの回答 は、さらに複雑なことを示唆しています。

単体テストを作成し、MoqやFakeItEasyなどのモックフレームワークを使用している場合(私が使用した最新の2つに名前を付けます)、インターフェイスを実装する別のクラスを暗黙的に作成しています。コードまたは静的分析を検索すると、実装は1つしかないことがわかりますが、実際には内部のモック実装があります。モックを書き始めると、インターフェースを抽出することが理にかなっていることがわかります。

しかし、待ってください。まだまだあります。暗黙のインターフェース実装があるシナリオは他にもあります。たとえば、.NETのWCF通信スタックを使用すると、リモートサービスへのプロキシが生成されます。これも、インターフェイスを実装しています。

クリーンなコード環境では、ここでの残りの回答に同意します。ただし、インターフェースを利用する可能性のあるフレームワーク、パターン、依存関係に注意してください。

62

いいえ、必要ありません。すべてのクラス参照のインターフェースを自動的に作成するのは、アンチパターンだと思います。

すべてに対してFoo/FooImplを作成するには、実際にコストがかかります。 IDEはインターフェイス/実装を無料で作成できますが、コードをナビゲートしているときは、foo.doSomething()のF3/F12からの追加の認識ロードがあり、必要な実際の実装ではなく、インターフェースの署名。

したがって、実際に何かのために必要な場合にのみ、それを行う必要があります。

次に、反論を取り上げます。

依存性注入フレームワークのインターフェイスが必要です

フレームワークをサポートするインターフェースはレガシーです。 Javaでは、インターフェースは以前は動的プロキシ(CGLIB以前)の要件でした。今日、あなたは通常それを必要としません。 EJB3やSpringなどで必要がなくなったことは、開発者の生産性の向上と恩恵と見なされます。

単体テストにはモックが必要です

独自のモックを作成し、2つの実際の実装がある場合は、インターフェースが適切です。コードベースにFooImplとTestFooの両方がある場合、おそらく最初からこの説明はありません。

ただし、Moq、EasyMock、Mockitoなどのモックフレームワークを使用している場合は、クラスをモックすることができ、インターフェースは必要ありません。これは、メソッドを割り当てることができる動的言語でfoo.method = mockImplementationを設定することに似ています。

依存関係の逆転の原則(DIP)に従うためのインターフェイスが必要です

DIPは、実装ではなくコントラクト(インターフェース)に依存するように構築することを示しています。しかし、クラスはすでにコントラクトと抽象化です。それがpublic/privateキーワードの目的です。大学では、標準的な例はMatrixまたはPolynomialクラスのようなものでした。コンシューマーは、マトリックスを作成したり、追加したりするためのパブリックAPIを持っていますが、マトリックスがスパースまたはデンス形式で実装されているかどうかを気にすることはできません。その点を証明するために必要なIMatrixまたはMatrixImplはありませんでした。

また、DIPは多くの場合、主要なモジュール境界だけでなく、すべてのクラス/メソッド呼び出しレベルで過剰に適用されます。 DIPを過剰に適用しているという兆候は、1つではなく2つのファイルを変更して変更する必要があるように、インターフェイスと実装がロックステップで変更されることです。 DIPが適切に適用されている場合は、インターフェースを頻繁に変更する必要がないことを意味します。また、別の兆候として、インターフェースには実際のコンシューマー(独自のアプリケーション)が1つしかありません。多くの異なるアプリで使用するためのクラスライブラリを構築している場合は、話は異なります。

これは、ボブマーティンおじさんがモックを描いている点の当然の結果です。主要な建築の境界でのみモックする必要があるはずです。 Webアプリケーションでは、HTTPおよびDBアクセスが主要な境界です。間にあるすべてのクラス/メソッド呼び出しはそうではありません。 DIPについても同様です。

以下も参照してください。

38
wrschneider

フェンスの両側の答えはこれで要約できるようです:

適切に設計し、インターフェイスが必要な場所にインターフェイスを配置します。

Yanniの答え への私の回答で述べたように、インターフェイスについて厳格で迅速なルールを持つことはできないと思います。ルールは、定義により、柔軟である必要があります。インターフェイスに関する私のルールは、APIを作成しているすべての場所でインターフェイスを使用する必要があることです。そして、APIは、責任のあるドメインから別のドメインに境界を越えているところならどこにでも作成する必要があります。

(ひどく不自然な)例として、Carクラスを構築しているとしましょう。クラスでは、UIレイヤーが必ず必要になります。この特定の例では、IginitionSwitchSteeringWheelGearShiftGasPedal、およびBrakePedalの形式を取ります。この車にはAutomaticTransmissionが含まれているため、ClutchPedalは必要ありません。 (これはひどい車なので、エアコン、ラジオ、座席はありません。実際には、床板もありません。そのハンドルにぶらさげて、最高のものを期待するだけです!)

これらのクラスのどれがインターフェースを必要とするのでしょうか?答えは、デザインによっては、すべての場合もあれば、まったくない場合もあります。

次のようなインターフェースを使用できます。

Interface ICabin
    Event IgnitionSwitchTurnedOn()
    Event IgnitionSwitchTurnedOff()
    Event BrakePedalPositionChanged(int percent)
    Event GasPedalPositionChanged(int percent)
    Event GearShiftGearChanged(int gearNum)
    Event SteeringWheelTurned(float degree)
End Interface

その時点で、これらのクラスの動作はICabinインターフェイス/ APIの一部になります。この例では、クラス(ある場合)はおそらく単純で、いくつかのプロパティと1つまたは2つの関数があります。また、設計で暗黙的に述べていることは、これらのクラスは、ICabinの具体的な実装をサポートするためだけに存在し、それ自体では存在できないか、ICabinコンテキストの外では意味がないということです。

プライベートメンバーをユニットテストしないのと同じ理由です-パブリックAPIをサポートするためにのみ存在するため、その動作は次のようにテストする必要がありますAPIのテスト。

したがって、クラスが別のクラスをサポートするためだけに存在し、概念的にはそれが独自のドメインを持たない本当にと見なす場合、インターフェイスをスキップしても問題ありません。ただし、クラスが十分に重要であり、独自のドメインを持つのに十分に成長していると考える場合は、先に進んでインターフェースを提供してください。


編集:

多くの場合(この回答を含む)、 'ドメイン'、 '依存関係'(頻繁に 'インジェクション'と組み合わせられます)のようなものを読みますが、これは、プログラムを開始したときに意味をなさない(確かにそうではありませんでした)私にとっては何かを意味します)。ドメインの場合、それはまさにそれがどのように聞こえるかを意味します:

支配権または権威が行使される領土。主権者または連邦などの所有物。比喩的にも使用されます。 [WordNetセンス2] [1913ウェブスター]

私の例では、IgnitionSwitchについて考えてみましょう。食肉処理車では、イグニッションスイッチは次の役割を果たします。

  1. ユーザーの認証(識別ではない)(正しいキーが必要)
  2. スターターに電流を供給して実際に車を始動できるようにする
  3. 作動を継続できるように点火システムに電流を供給する
  4. 車が停止するように電流を遮断します。
  5. 見方に応じて、ほとんどの(すべての?)新しい車には、トランスミッションがパーク外にあるときにキーがイグニッションから外れないようにするスイッチがあるため、これはそのドメインの一部である可能性があります。 (実際には、システムを再考して再設計する必要があることを意味します...)

これらのプロパティは、IgnitionSwitchdomainを構成します。つまり、それが認識し、責任を負うものです。

IgnitionSwitchGasPedalの責任を負いません。イグニッションスイッチは、あらゆる点で、アクセルペダルを完全に認識していません。どちらも完全に独立して動作します(ただし、両方がなければ車はかなり価値がありません!)。

最初に述べたように、それはあなたのデザインに依存します。オン(IgnitionSwitch)とオフ(True)の2つの値を持つFalseを設計できます。または、提供されたキーと他のアクションのホストを認証するように設計することもできます。それが開発者であることの難しい部分は、砂のどこに線を引くかを決定することです-そして正直なところ、ほとんどの場合、それは完全に相対的です。砂の中のこれらの行は重要です-それはあなたのAPIがある場所であり、したがってあなたのインターフェースがあるべき場所です。

22
Wayne Werner

いいえ (YAGNI) 。ただし、このインターフェイスを使用して他のクラスのテストを作成することを計画している場合を除き、これらのテストはインターフェイスをモックすることでメリットが得られます。

8
stijn

から [〜#〜] msdn [〜#〜]

インターフェースは、特定の機能を提供するために、アプリケーションが関連しない可能性のある多くのオブジェクトタイプを必要とする状況に適しています。

複数のインターフェースを実装できる単一の実装を定義できるため、インターフェースは基本クラスよりも柔軟性があります。

インターフェイスは、基本クラスから実装を継承する必要がない場合に適しています。

インターフェイスは、クラス継承を使用できない場合に役立ちます。たとえば、構造はクラスから継承できませんが、インターフェースを実装できます。

一般に、単一クラスの場合、インターフェースを実装する必要はありませんが、プロジェクトの将来を考慮すると、クラスの必要な動作を正式に定義すると役立つ場合があります。

8
lwm

あなたがこの質問をしたので、あなたはすでに複数の実装を隠すインターフェースを持っていることに興味を持っていると思います。これは、依存関係の逆転の原理によって明らかになります。

ただし、インターフェースの有無は、実装数に依存しません。インターフェイスの本当の役割は、それがどのように実装されるべきかではなく、どのサービスが提供されるべきかを述べたコントラクトを定義することです。

契約が定義されると、2つ以上のチームが独立して作業できます。モジュールAで作業していて、モジュールBに依存しているとします。Bにインターフェースを作成すると、すべての詳細がインターフェースに隠されているため、Bの実装を心配することなく作業を続行できます。これにより、分散プログラミングが可能になります。

モジュールBにはインターフェースの実装が1つしかありませんが、インターフェースはまだ必要です。

結論として、インターフェイスは実装の詳細をユーザーから隠します。インターフェイスへのプログラミングは、契約を定義する必要があるため、より多くのドキュメントを記述し、より多くのモジュール式ソフトウェアを記述し、単体テストを促進し、開発速度を加速するのに役立ちます。

5
Hui Wang

質問に答えるには、それだけではありません。

インターフェイスの重要な側面の1つはインテントです。

インターフェースは「データを含まないが動作を公開する抽象型」- Interface(computing) したがって、これがクラスがサポートする動作または動作のセットである場合、インターフェースはおそらく正しいパターンです。ただし、その振る舞いがクラスによって具体化された概念に固有のものである場合は、おそらくインターフェースをまったく必要としません。

最初に尋ねる質問は、表現しようとしているものまたはプロセスの性質は何ですか。次に、その性質を所定の方法で実装する実際的な理由を説明します。

5
Joshua Drake

ここでの答えはすべて非常に良いです。実際、ほとんどの場合、別のインターフェースを実装する必要はありません。しかし、あなたがとにかくそれをしたい場合があります。ここに私がそれをするいくつかのケースがあります:

クラスは、公開したくない別のインターフェースを実装しています
サードパーティのコードをブリッジするアダプタクラスで頻繁に発生します。

interface NameChangeListener { // Implemented by a lot of people
    void nameChanged(String name); 
} 

interface NameChangeCount { // Only implemented by my class
    int getCount();
}

class NameChangeCounter implements NameChangeListener, NameChangeCount {
    ...
}

class SomeUserInterface {
    private NameChangeCount currentCount; // Will never know that you can change the counter
}

クラスは、谷を漏らしてはならない特定のテクノロジーを使用しています
主に外部ライブラリとやり取りするとき。実装が1つしかない場合でも、インターフェイスを使用して、外部ライブラリとの不要な結合を導入しないようにします。

interface SomeRepository { // Guarantee that the external library details won't leak trough
    ...
}

class OracleSomeRepository implements SomeRepository { 
    ... // Oracle prefix allow us to quickly know what is going on in this class
}

クロスレイヤー通信
たとえ1つのUIクラスだけがドメインクラスの1つを実装する場合でも、それらのレイヤー間の分離を改善し、最も重要なことに循環依存を回避します。

package project.domain;

interface UserRequestSource {
    public UserRequest getLastRequest();
}

class UserBehaviorAnalyser {
    private UserRequestSource requestSource;
}

package project.ui;

class OrderCompleteDialog extends SomeUIClass implements project.domain.UserRequestSource {
    // UI concern, no need for my domain object to know about this method.
    public void displayLabelInErrorMode(); 

    // They most certainly need to know about *that* though
    public UserRequest getLastRequest();
}

メソッドのサブセットのみがほとんどのオブジェクトで使用可能である必要があります
ほとんどの場合、具象クラスに構成メソッドがあると発生します

interface Sender {
    void sendMessage(Message message)
}

class PacketSender implements Sender {
    void sendMessage(Message message);
    void setPacketSize(int sizeInByte);
}

class Throttler { // This class need to have full access to the object
    private PacketSender sender;

    public useLowNetworkUsageMode() {
        sender.setPacketSize(LOW_PACKET_SIZE);
        sender.sendMessage(new NotifyLowNetworkUsageMessage());

        ... // Other details
    }
}

class MailOrder { // Not this one though
    private Sender sender;
}

したがって、最終的に私はプライベートフィールドを使用するのと同じ理由でインターフェイスを使用します。他のオブジェクトは、アクセスしてはいけないものにアクセスできません。そのようなケースがある場合は、oneクラスのみがそれを実装していても、インターフェースを紹介します。

インターフェースは本当に重要ですが、あなたが持っているそれらの数を制御するようにしてください。

ほぼすべてのインターフェースを作成する道を歩んできたので、「途切れたスパゲッティ」コードになってしまいがちです。私はこの主題についていくつかの非常に賢明な言葉を投稿したアイエンデ・ラヒエンのより大きな知恵に頼ります:

http://ayende.com/blog/153889/limit-your-abstractions-analyzing-a-ddd-application

これはシリーズ全体の彼の最初の投稿なので、読み続けてください!

3
Lord Spa

何かをする本当の理由はありません。インターフェースは、出力プログラムではなく、あなたを助けるためのものです。したがって、インターフェイスが100万のクラスで実装されている場合でも、作成する必要があるというルールはありません。あなたまたはあなたのコードを使用する他の誰かがすべての実装に浸透する何かを変更したいときにそれを作成します。インターフェースを作成すると、それを実装する別のクラスを作成する可能性があるすべての将来のケースで役立ちます。

2
John Demetriou

この場合でもインターフェースを導入したい理由の1つは、 依存関係の逆転の原則 に従うことです。つまり、クラスを使用するモジュールは、具体的な実装ではなく、その抽象化(つまり、インターフェース)に依存します。高レベルのコンポーネントと低レベルのコンポーネントを分離します。

2
dodgy_coder

クラスのインターフェースを定義する必要は必ずしもありません。

値オブジェクトのような単純なオブジェクトには、複数の実装はありません。彼らはまた、あざける必要はありません。実装はそれ自体でテストでき、それらに依存する他のクラスをテストする場合は、実際の値オブジェクトを使用できます。

インターフェイスの作成にはコストがかかることを忘れないでください。実装に沿って更新する必要があり、追加のファイルが必要です。また、一部のIDEは、インターフェースではなく、実装を拡大するのに問題があります。

したがって、実装からの抽象化が必要な、より高いレベルのクラスのインターフェースのみを定義します。

クラスを使用すると、インターフェースを無料で取得できます。実装の他に、クラスはパブリックメソッドのセットからインターフェイスを定義します。そのインターフェースは、すべての派生クラスによって実装されます。厳密に言えばインターフェースではありませんが、まったく同じ方法で使用できます。したがって、クラスの名前ですでに存在するインターフェースを再作成する必要はないと思います。

1
Florian F