「インターフェイスにプログラムする」とはどういう意味ですか?


814

これについて何度か言及しましたが、その意味がよくわかりません。いつ、なぜこれを行うのですか?

私はインターフェースが何をするのか知っていますが、これがはっきりしないので、それらを正しく使用することに失敗していると思います。

あなたがやろうと思ったなら、それはそうですか?

IInterface classRef = new ObjectWhatever()

を実装する任意のクラスを使用できますかIInterface?いつそれを行う必要がありますか?私が考えることができる唯一のことは、あなたがメソッドを持っていて、それが実装することを除いて、どのオブジェクトが渡されるかわからない場合ですIInterface。どれくらいの頻度でそうする必要があるか、私には思えません。

また、インターフェイスを実装するオブジェクトを受け取るメソッドをどのように記述できますか?それは可能ですか?


3
覚えていてプログラムを最適化する必要がある場合は、コンパイルの直前に、インターフェイス宣言を実際の実装に交換することをお勧めします。インターフェースを使用すると、パフォーマンスに影響を与える間接レベルが追加されます。ただし、プログラムされたコードをインターフェイスに配布する...
Ande Turner

18
@アンデターナー:それは悪いアドバイスです。1)。「あなたのプログラムは最適である必要がある」はインターフェースを交換するための良い理由ではありません!次に、「インターフェイスにプログラムされたコードを配布します...」と言うので、与えられた要件(1)にアドバイスし、次に最適でないコードをリリースしますか?!?
ミッチウィート

74
ここでの答えのほとんどは完全に正しくありません。「インターフェイスキーワードを使用する」ことを意味することも、意味することもありません。インターフェイスは、契約の同義語(ルックアップ)の使用方法の仕様です。それとは別に、その契約がどのように履行されるかという実装です。メソッド/タイプの保証のみに対してプログラムすることで、メソッド/タイプがまだ規約に準拠した方法で変更された場合に、それを使用するコードが破損しないようにします。
jyoungdev 2012

2
@ apollodude217は、実際にはページ全体で最良の回答です。少なくともタイトルの質問については、ここには少なくとも3つのまったく異なる質問があります...
Andrew Spencer

4
このような質問の根本的な問題は、「インターフェースへのプログラミング」が「抽象インターフェースですべてをラップする」ことを意味することを想定していることです。これは、Javaスタイルの抽象インターフェースの概念よりも前から考えるとばかげています。
ジョナサンアレン

回答:


1634

この質問に対するいくつかの素晴らしい答えがあり、インターフェースや疎結合コード、制御の反転などについて、あらゆる種類の詳細に触れています。かなりやりがいのある議論がいくつかあるので、インターフェイスを使用する理由を理解するために、少し機会を割いてみましょう。

私が最初にインターフェースに触れ始めたとき、私もそれらの関連性について混乱していました。なぜそれらが必要なのか理解できませんでした。JavaやC#などの言語を使用している場合、私たちはすでに継承を行っているので、インターフェイスを継承の弱い形式と見なし、「なぜわざわざ?」と考えました。ある意味では、インターフェースは弱い継承の一種と考えることができますが、それを超えて、それらが示す共通の特性または動作を分類する手段として考えることで、言語構造としての使用を理解しました潜在的に多くの関連しないオブジェクトのクラス。

たとえば、SIMゲームがあり、次のクラスがあるとします。

class HouseFly inherits Insect {
    void FlyAroundYourHead(){}
    void LandOnThings(){}
}

class Telemarketer inherits Person {
    void CallDuringDinner(){}
    void ContinueTalkingWhenYouSayNo(){}
}

明らかに、これらの2つのオブジェクトには、直接継承という点で共通点はありません。しかし、どちらも迷惑であると言えます。

私たちのゲームには、ゲームプレーヤーが夕食をとるときにゲームプレイヤーを困らせるような、ある種のランダムなものが必要だとしましょう。これはa HouseFlyまたはa Telemarketerまたは両方の可能性がありますが、単一の関数で両方をどのように許可するのですか?そして、どのようにして、オブジェクトの種類ごとに、同じように「迷惑なことをする」ように依頼しますか?

を理解するための鍵は、との両方が、それらをモデル化するという点では似ていなくても、緩やかに解釈される一般的な動作TelemarketerHouseFly共有することです。それで、両方が実装できるインターフェースを作ってみましょう:

interface IPest {
    void BeAnnoying();
}

class HouseFly inherits Insect implements IPest {
    void FlyAroundYourHead(){}
    void LandOnThings(){}

    void BeAnnoying() {
        FlyAroundYourHead();
        LandOnThings();
    }
}

class Telemarketer inherits Person implements IPest {
    void CallDuringDinner(){}
    void ContinueTalkingWhenYouSayNo(){}

    void BeAnnoying() {
        CallDuringDinner();
        ContinueTalkingWhenYouSayNo();
    }
}

現在、2つのクラスがあり、それぞれが独自の方法で迷惑な場合があります。そして、それらは同じ基本クラスから派生し、共通の固有の特性を共有する必要はありません-彼らは単に契約を満たす必要がありますIPest-その契約は単純です。あなたはただしなければならないBeAnnoying。この点で、次のモデルを作成できます。

class DiningRoom {

    DiningRoom(Person[] diningPeople, IPest[] pests) { ... }

    void ServeDinner() {
        when diningPeople are eating,

        foreach pest in pests
        pest.BeAnnoying();
    }
}

ここには、多数のダイナーと多数の害虫を受け入れるダイニングルームがあります。インターフェイスの使用に注意してください。これは、小さな世界では、pests配列のメンバーは実際にはTelemarketerオブジェクトまたはオブジェクトである可能性があることを意味しHouseFlyます。

このServeDinnerメソッドは、ディナーが提供され、ダイニングルームにいる私たちの人々が食べることになっているときに呼び出されます。私たちの小さなゲームでは、そのとき私たちの害虫は彼らの仕事をします-各害虫はIPestインターフェースを介して迷惑になるように指示されています。このように、私たちは簡単に両方を持つことができますTelemarketersHouseFlys、それぞれの方法で迷惑になります。DiningRoom害虫オブジェクトにます、それが何であるかを本当に気にせず、何もない可能性があります他と共通。

この非常に工夫された疑似コードの例(予想よりもずっと長い間ドラッグされたもの)は、インターフェイスをいつ使用するかという観点から、最終的にライトをオンにした種類を説明するためのものです。例の愚かさをあらかじめお詫びしますが、それが理解に役立つことを願っています。そして、確かに、あなたがここで受け取った他の投稿された回答は、設計パターンと開発方法論における今日のインターフェースの使用の全範囲を本当にカバーしています。


3
考慮すべきもう1つのことは、場合によっては、「面倒」になる可能性のあるものに対するインターフェースがあり、さまざまなオブジェクトBeAnnoyingが何もしないものとして実装されると便利な場合があることです。このインタフェースの代わりに存在してもよく、またはそれに加えて、迷惑しているもののためのインターフェースは、(両方のインターフェイスが存在する場合は、「物事ある迷惑な」インターフェース「は、物事を継承する必要があるかもしれませんインターフェイス迷惑」)。そのようなインターフェースを使用することの欠点は、実装が「煩わしい」数のスタブメソッドを実装する負担を負う可能性があることです。利点は...
スーパーキャット2012

4
メソッドは抽象的なメソッドを表すことを意図していません-それらの実装はインターフェースに焦点を合わせた質問とは無関係です。
Peter Meyer

33
IPestなどのカプセル化動作は、誰かがその主題についてより多くの資料をフォローアップすることに興味がある場合の戦略パターンとして知られています...
nckbrz

9
興味深いことに、のオブジェクトIPest[]はIPest参照であるBeAnnoying()ため、そのメソッドがあるので呼び出すことができますが、キャストなしで他のメソッドを呼び出すことはできません。ただし、各オブジェクトの個々のBeAnnoying()メソッドが呼び出されます。
D.ベンノーブル2015年

4
非常に良い説明...私はここでそれを言う必要があります:インターフェイスがなんらかの緩やかな継承メカニズムであると聞いたことはありませんが、代わりに継承がインターフェイスを定義するための貧弱なメカニズムとして使用されていることを知っています(たとえば、通常のPythonでは常にそれを行う)。
カルロスHロマーノ

283

私が学生に与えた具体的な例は、彼らが書くべきだということです

List myList = new ArrayList(); // programming to the List interface

の代わりに

ArrayList myList = new ArrayList(); // this is bad

これらは短いプログラムでもまったく同じように見えますが、プログラムでmyList100回使用し続けると、違いがわかり始めます。最初の宣言によりmyListListインターフェースで定義されているメソッドのみを呼び出すことが保証されます(ArrayList特定のメソッドはありません)。この方法でインターフェースにプログラミングした場合、後で本当に必要なことを決定できます

List myList = new TreeList();

そして、その1つの場所でコードを変更するだけで済みます。インターフェイスにプログラミングしたため、コードの残りの部分は、実装を変更することによって破壊されることは何もしないことをすでに知っています

メソッドのパラメーターと戻り値について話していると、利点はさらに明白になります(私は思います)。これを例にとります:

public ArrayList doSomething(HashMap map);

このメソッド宣言は、2つの具体的な実装(ArrayListおよびHashMap)に結び付けます。そのメソッドが他のコードから呼び出されるとすぐに、それらの型を変更すると、おそらく呼び出しコードも変更する必要があります。インターフェイスにプログラムする方が良いでしょう。

public List doSomething(Map map);

これで、どのような種類のList戻り値、またはどのような種類のMapパラメータが渡されるかは関係ありません。doSomethingメソッド内で変更を加えても、呼び出しコードを変更する必要はありません。


コメントは詳細な議論のためのものではありません。この会話はチャットに移動しました
イベット

非常に明確な説明。非常に役立つ
サミュエルルスワタ

「最初の宣言では、Listインターフェースで定義されたmyListのメソッドのみが呼び出されることを保証します(したがって、ArrayList固有のメソッドはありません)。この方法でインターフェースにプログラミングした場合、後でリストmyList = new TreeList();が本当に必要であると判断でき、その1か所でコードを変更するだけで済みます。」たぶん私は誤解しているかもしれませんが、「myListのメソッドのみを呼び出すようにするには」なぜArrayListをTreeListに変更する必要があるのでしょうか。
user3014901

1
@ user3014901使用しているリストのタイプを変更する理由はいくつもあります。たとえば、ルックアップのパフォーマンスが向上する場合があります。ポイントは、Listインターフェイスにプログラムすると、後でコードを別の実装に簡単に変更できることです。
トカゲに請求する

73

インターフェースへのプログラミングは、「私はこの機能が必要で、どこから来たのか気にしません」と言っています。

(Javaで)ListインターフェースとArrayListLinkedList具象クラスを検討してください。私が気にしているのが、反復によってアクセスする必要がある複数のデータ項目を含むデータ構造があることだけなら、私はList(そしてそれは99%の時間です)を選択します。リストのいずれかの端から一定時間の挿入/削除が必要であることがわかっている場合は、LinkedList具体的な実装を選択する可能性があります(または、より可能性が高い場合は、Queueインターフェイスを使用します)。インデックスによるランダムアクセスが必要なことがわかっている場合は、ArrayList具象クラスを選択します。


1
つまり、何が行われるか、どのように行われるかの独立性です。独立したコンポーネントに沿ってシステムを分割することにより、シンプルで再利用可能なシステムになります(Clojureを作成した人によるSimple Made Easyを参照)
beluchin

38

インターフェイス間の使用は、クラス間の不要な結合を削除することに加えて、コードを簡単にテストできるようにするための重要な要素です。クラスの操作を定義するインターフェースを作成することにより、その機能を使用したいクラスが、実装クラスに直接依存することなく、その機能を使用できるようになります。後で別の実装を変更して使用する場合は、実装がインスタンス化されているコードの部分を変更するだけで済みます。残りのコードは、実装クラスではなくインターフェースに依存するため、変更する必要はありません。

これは単体テストの作成に非常に役立ちます。テスト中のクラスでは、インターフェイスに依存し、コンストラクターまたはプロパティセッターを介して、インターフェイスのインスタンスをクラス(または必要に応じてインターフェイスのインスタンスを構築できるファクトリー)に注入します。クラスは、提供された(または作成された)インターフェースをメソッドで使用します。テストを作成するときに、インターフェイスをモックまたは偽造して、単体テストで構成されたデータで応答するインターフェイスを提供できます。これを行うことができるのは、テスト対象のクラスがインターフェースのみを扱い、具体的な実装ではないためです。あなたのモックや偽のクラスを含む、インターフェースを実装するあらゆるクラスが実行します。

編集:以下は、Erich Gammaが「実装ではなくインターフェイスへのプログラム」という彼の引用について論じている記事へのリンクです。

http://www.artima.com/lejava/articles/designprinciples.html


3
このインタビューをもう一度お読みください。ガンマはもちろん、インターフェースのオブジェクト指向の概念について話していました。JAVAやC#の特殊なクラス(ISomething)ではありませんでした。問題は、ほとんどの人が彼がキーワードについて話していたので、今では多くの不要なインターフェース(ISomething)があります。
Sylvain Rodrigue

とても良いインタビューです。今後の読者のために注意してください。インタビューには4ページあります。表示される前にブラウザをほぼ閉じてしまいます。
Ad Infininitum 2017

38

インターフェイスへのプログラミングは、Javaや.NETで見られるような抽象インターフェイスとはまったく関係ありません。OOPの概念すらありません。

それが意味することは、オブジェクトまたはデータ構造の内部をいじり回さないことです。抽象プログラムインターフェイス(API)を使用して、データを操作します。JavaまたはC#では、生のフィールドアクセスの代わりにパブリックプロパティとメソッドを使用することを意味します。Cでは、生のポインタの代わりに関数を使用することを意味します。

編集:データベースでは、テーブルへの直接アクセスの代わりにビューとストアドプロシージャを使用することを意味します。


5
ベストアンサー。ガンマはここで同様の説明をします:artima.com/lejava/articles/designprinciples.html(2ページを参照)。彼はオブジェクト指向の概念に言及していますが、あなたは正しいです。それはそれよりも大きいです。
Sylvain Rodrigue

36

制御の反転を調べる必要があります。

このようなシナリオでは、次のように記述しません。

IInterface classRef = new ObjectWhatever();

あなたはこのようなものを書くでしょう:

IInterface classRef = container.Resolve<IInterface>();

これは、ルールベースのセットアップに入るでしょう containerオブジェクトの ObjectWhateverなどの実際のオブジェクトを作成します。重要なことは、このルールを別のタイプのオブジェクトを使用するものに置き換えることができ、コードは引き続き機能することです。

IoCをテーブルから外すと、特定の何かを実行するオブジェクトとは通信できるが、どのタイプのオブジェクトまたはどのように実行できるかを認識できるコードを記述できます。

これは、パラメータを渡すときに便利です。

括弧で囲まれた質問「また、インターフェイスを実装するオブジェクトを取り込むメソッドをどのように記述できますか?それは可能ですか?」については、C#では、次のように、パラメーターの型にインターフェイスの型を使用するだけです。

public void DoSomethingToAnObject(IInterface whatever) { ... }

これは、「特定のことを行うオブジェクトとの対話」に直接つながります。上記で定義されたメソッドは、IInterfaceですべてを実装することをオブジェクトに期待することを知っていますが、それがどのタイプのオブジェクトであるかは関係なく、コントラクトに準拠していること、つまりインターフェイスが何であるかを認識しています。

たとえば、あなたはおそらく電卓に精通していて、おそらくあなたの日々にかなりの数を使ってきましたが、ほとんどの場合それらはすべて異なっています。一方、標準の計算機がどのように機能するかを知っているので、他の計算機にはない特定の機能を使用できない場合でも、それらをすべて使用できます。

これがインターフェースの美しさです。特定の動作を期待できるオブジェクトが渡されることを知っているコードを書くことができます。それは、どんな種類のオブジェクトであるかを気にせず、必要な動作をサポートするだけです。

具体的な例を挙げましょう。

Windowsフォーム用にカスタマイズされた翻訳システムがあります。このシステムは、フォーム上のコントロールをループして、それぞれのテキストを翻訳します。システムは、「type-of-control-that-has-a-Text-property」などの基本的なコントロールを処理する方法を知っていますが、基本的なものには不十分です。

ここで、コントロールは制御できない事前定義されたクラスから継承するため、次の3つのいずれかを実行できます。

  1. 私たちの翻訳システムのサポートを構築して、どの種類のコントロールを使用しているかを具体的に検出し、正しいビットを翻訳します(メンテナンスの悪夢)
  2. 基本クラスへのサポートの構築(すべてのコントロールが異なる事前定義クラスから継承するため不可能)
  3. インターフェースのサポートを追加する

したがって、nrを実行しました。3.すべてのコントロールはILocalizableを実装します。これは、「自分自身」を翻訳テキスト/ルールのコンテナーに翻訳する1つの方法を提供するインターフェイスです。そのため、フォームは、特定のインターフェイスを実装し、コントロールをローカライズするために呼び出すことができるメソッドがあることを知っているだけで、どの種類のコントロールを見つけたのかを知る必要はありません。


31
これが混乱を増すだけなので、なぜ最初にIoCについて言及するのですか?
Kevin Le-Khnle 2008

1
同意しますが、インターフェイスに対するプログラミングは、IoCを簡単かつ信頼性の高いものにするための手法にすぎないと思います。
terjetyl 2008

28

実装ではなくインターフェースへのコードは、Javaやそのインターフェース構成要素とは何の関係もありません。

このコンセプトは、Patterns / Gang of Fourの本で際立っていましたが、おそらくそれよりずっと前にあったでしょう。この概念は、Javaが登場するずっと前から存在していました。

Javaインターフェース構成は、このアイデアを支援するために(とりわけ)作成されたものであり、人々は本来の意図ではなく意味の中心として構成に集中しすぎています。ただし、Java、C ++、C#などにパブリックおよびプライベートのメソッドと属性があるのはそのためです。

オブジェクトまたはシステムのパブリックインターフェイスと対話するだけです。内部でどのように機能するかを心配したり、予測したりしないでください。実装方法を気にする必要はありません。オブジェクト指向のコードでは、パブリックメソッドとプライベートメソッド/属性があるのはそのためです。プライベートメソッドはクラス内で内部的にのみ使用されるため、パブリックメソッドを使用することを意図しています。それらはクラスの実装を構成し、パブリックインターフェイスを変更せずに必要に応じて変更できます。機能に関しては、クラスのメソッドが同じ操作を実行し、同じパラメーターで呼び出すたびに同じ期待される結果が得られると想定します。これにより、作成者は、ユーザーがクラスと対話する方法を壊すことなく、クラスの機能と実装を変更できます。

そして、インターフェース構造を使用せずに実装ではなく、インターフェースにプログラムすることができます。C ++の実装ではなく、インターフェイス構造を持たないインターフェイスにプログラミングできます。2つの大規模なエンタープライズシステムは、システム内部のオブジェクトのメソッドを呼び出すのではなく、パブリックインターフェイス(契約)を介して相互作用する限り、より堅牢に統合できます。同じ入力パラメーターが与えられた場合、インターフェースは常に同じ期待される方法で反応することが期待されます。実装ではなくインターフェースに実装されている場合。このコンセプトは多くの場所で機能します。

Javaインターフェースには、「実装ではなく、インターフェースへのプログラム」という概念を使用してこれまでに何でもできるという考えを揺るがしてください。それらは概念の適用を助けることができますが、それらは概念ではありません


1
最初の文はそれをすべて言います。これは受け入れられる答えになるはずです。
madumlao

14

インターフェースの仕組みは理解しているように見えますが、インターフェースをいつ使用するか、どのような利点があるのか​​がわかりません。インターフェースが意味を持つときの例をいくつか示します。

// if I want to add search capabilities to my application and support multiple search
// engines such as Google, Yahoo, Live, etc.

interface ISearchProvider
{
    string Search(string keywords);
}

次に、GoogleSearchProvider、YahooSearchProvider、LiveSearchProviderなどを作成できます。

// if I want to support multiple downloads using different protocols
// HTTP, HTTPS, FTP, FTPS, etc.
interface IUrlDownload
{
    void Download(string url)
}

// how about an image loader for different kinds of images JPG, GIF, PNG, etc.
interface IImageLoader
{
    Bitmap LoadImage(string filename)
}

次に、JpegImageLoader、GifImageLoader、PngImageLoaderなどを作成します。

ほとんどのアドインとプラグインシステムは、インターフェイスを介して動作します。

もう1つの一般的な用途は、リポジトリパターンです。異なるソースから郵便番号のリストをロードしたいとします

interface IZipCodeRepository
{
    IList<ZipCode> GetZipCodes(string state);
}

次に、XMLZipCodeRepository、SQLZipCodeRepository、CSVZipCodeRepositoryなどを作成します。Webアプリケーションの場合、SQLデータベースの準備が整う前に何かを起動して実行できるように、XMLリポジトリを早い段階で作成することがよくあります。データベースの準備ができたら、SQLRepositoryを記述してXMLバージョンを置き換えます。インターフェイスの外でのみ実行されるため、残りのコードは変更されていません。

メソッドは次のようなインターフェースを受け入れることができます。

PrintZipCodes(IZipCodeRepository zipCodeRepository, string state)
{
    foreach (ZipCode zipCode in zipCodeRepository.GetZipCodes(state))
    {
        Console.WriteLine(zipCode.ToString());
    }
}

10

同様のクラスのセットがある場合、コードがはるかに拡張可能になり、保守が容易になります。私はジュニアプログラマーなので、私は専門家ではありませんが、似たようなものが必要なプロジェクトを終えたところです。

私は、医療機器を実行しているサーバーと通信するクライアント側のソフトウェアに取り組んでいます。お客様が時々構成しなければならないいくつかの新しいコンポーネントを備えた、このデバイスの新しいバージョンを開発しています。新しいコンポーネントには2つのタイプがあり、それらは異なりますが、非常によく似ています。基本的に、2つの構成フォーム、2つのリストクラス、2つすべてを作成する必要がありました。

私は、実際のロジックのほとんどすべてを保持するコントロールタイプごとに抽象基本クラスを作成し、2つのコンポーネントの違いに対応するために派生タイプを作成するのが最善だと判断しました。ただし、常に型について心配する必要があった場合、基本クラスはこれらのコンポーネントに対して操作を実行できませんでした(そうかもしれませんが、すべてのメソッドに "if"ステートメントまたはスイッチがありました)。 。

これらのコンポーネントの単純なインターフェースを定義し、すべての基本クラスがこのインターフェースと通信します。今、私が何かを変更すると、それはどこでもほぼ「うまくいく」だけで、コードの重複はありません。


10

世の中にはたくさんの説明がありますが、もっと簡単にするためです。インスタンスAを見てみましょうList。次のようにリストを実装できます:

  1. 内部配列
  2. リンクされたリスト
  3. その他の実装

インターフェースを構築することにより、と言いますList。リストの定義またはList実際の意味についてのみコーディングします。

内部的に実装と言っても、どのタイプの実装でも使用できますarray。しかし、なんらかの理由でバグやパフォーマンスなど、実装を変更したいとします。次に、宣言List<String> ls = new ArrayList<String>()をに変更する必要がありますList<String> ls = new LinkedList<String>()

コードの他の場所では、他に変更する必要はありません。それ以外はすべての定義に基づいて構築されているためですList


8

Javaでプログラミングする場合、JDBCが良い例です。JDBCは一連のインターフェースを定義しますが、実装については何も述べていません。アプリケーションは、この一連のインターフェースに対して作成できます。理論的には、JDBCドライバーを選択すれば、アプリケーションは機能します。より高速または「より良い」またはより安価なJDBCドライバーがあるか、何らかの理由で発見された場合でも、理論的にはプロパティファイルを再構成できます。アプリケーションに変更を加えなくても、アプリケーションは引き続き機能します。


これは、より優れたドライバーが利用可能になった場合に役立つだけでなく、データベースベンダーを完全に変更することを可能にします。
ケン劉

3
JDBCは非常に悪いため、置き換える必要があります。別の例を見つけてください。
Joshua

JDBCは悪いですが、何らかの理由で、インターフェースと実装または抽象化のレベルとの関係はありません。そして、問題の概念を説明するために、それは完璧です。
Erwin Smout

8

インターフェイスへのプログラミングは素晴らしいです。疎結合を促進します。@lassevkが述べたように、Inversion of Controlはこれの優れた使用法です。

さらに、SOLIDプリンシパルを調べますここにビデオシリーズがあります

ハードコーディングされた(強結合の例)を経てインターフェースを調べ、最後にIoC / DIツール(NInject)に進みます


7

私はこの質問に遅れをとっていますが、「実装ではなくインターフェイスへのプログラム」という行がGoF(Gang of Four)Design Patternsブックで良い議論があったことをここで述べたいと思います。

それは、p。18:

実装ではなくインターフェースへのプログラム

変数を特定の具象クラスのインスタンスとして宣言しないでください。代わりに、抽象クラスによって定義されたインターフェースのみにコミットしてください。これは、この本のデザインパターンの共通のテーマであることがわかります。

そしてその上で、それは始まりました:

抽象クラスによって定義されたインターフェースの観点からのみ、オブジェクトを操作することには2つの利点があります。

  1. オブジェクトがクライアントが期待するインターフェイスに準拠している限り、クライアントは、使用するオブジェクトの特定のタイプを認識しません。
  2. クライアントは、これらのオブジェクトを実装するクラスを認識しません。クライアントは、インターフェイスを定義する抽象クラスについてのみ知っています。

つまり、言い換えると、quack()アヒル用のメソッドを持ち、次にbark()犬用のメソッドを持つようにクラスを記述しないでください。それらはクラス(またはサブクラス)の特定の実装に対してあまりにも具体的であるためです。代わりに、のような基本クラスで使用される一般的な十分なされている名前を、使用方法を記述しgiveSound()たりmove()、彼らはアヒル、犬、あるいは車のために使用することができるようにして、あなたのクラスのクライアントだけで言うことができる.giveSound()のではなくオブジェクトに送信する正しいメッセージを発行する前に、使用するquack()bark()、タイプを決定するかについて考えます。


6

既に選択されている回答(およびここにあるさまざまな有益な投稿)に加えて、Head First Design Patternsのコピーを入手することを強くお勧めします。これは非常に読みやすく、質問に直接答え、なぜそれが重要であるかを説明し、その原則(およびその他)を利用するために使用できる多くのプログラミングパターンを示します。


5

既存の投稿に追加するために、開発者が同時に別々のコンポーネントで作業する場合、インターフェイスへのコーディングが大規模プロジェクトで役立つことがあります。必要なのは、インターフェースを事前に定義し、それらにコードを書き込むことです。他の開発者は、実装しているインターフェースにコードを書き込みます。


4

単体テストにも適しています。独自のクラス(インターフェースの要件を満たすクラス)を、それに依存するクラスに挿入できます。


4

抽象化に依存していない場合でも、インターフェイスへのプログラミングは有利です。

インターフェイスへのプログラミングでは、コンテキストに応じて適切なオブジェクトのサブセットを使用する必要があります。それが役立つのは:

  1. 文脈的に不適切なことをするのを防ぎ、
  2. 将来的に安全に実装を変更できます。

たとえばPersonFriendおよびEmployeeインターフェースを実装するクラスを考えます。

class Person implements AbstractEmployee, AbstractFriend {
}

人の誕生日のコンテキストでは、その人をのFriendように扱うことを防ぐために、インターフェースにプログラムしますEmployee

function party() {
    const friend: Friend = new Person("Kathryn");
    friend.HaveFun();
}

人の仕事のコンテキストでは、Employee職場の境界がぼやけるのを防ぐために、インターフェースにプログラムします。

function workplace() {
    const employee: Employee = new Person("Kathryn");
    employee.DoWork();
}

すごい。私たちはさまざまな状況で適切に行動しており、ソフトウェアはうまく機能しています。

遠い将来、私たちのビジネスが犬と協力するように変更された場合、ソフトウェアをかなり簡単に変更できます。まず、およびのDog両方を実装するクラスを作成します。その後、無事にに変更します。両方の関数に数千行のコードが含まれている場合でも、次のことが当てはまることがわかっているため、この単純な編集は機能します。FriendEmployeenew Person()new Dog()

  1. 関数partyはのFriendサブセットのみを使用しますPersonます。
  2. 関数workplaceはのEmployeeサブセットのみを使用しますPersonます。
  3. クラスDogFriendEmployeeインターフェースの両方を実装します。

一方、いずれかpartyまたはworkplaceに対してプログラミングしPersonた場合、両方に- Person固有のコードが含まれるリスクがあります。からPersonに変更するDogと、コードをくまなくPerson調べDogて、サポートされていない特定のコードを抽出する必要があります。

道徳的な:インターフェースへのプログラミングは、コードが適切に動作し、変更の準備ができるようにするのに役立ちます。また、抽象化に依存するようにコードを準備するため、さらに多くの利点があります。


1
あなたが過度に広いインターフェースを持っていないと仮定すると、それはです。
ケーシー

4

Swimmer機能を追加するために新しいクラスを作成していてクラスswim()のオブジェクトを使用する必要がある場合、DogこのDogクラスはAnimal宣言するインターフェイスを実装しますswim()

階層の上部(Animal)は非常に抽象的であり、下部(Dog)は非常に具体的です。「インターフェースへのプログラミング」について私が考える方法は、Swimmerクラスを作成するときに、この階層ではAnimalオブジェクトの階層にあるインターフェースに対してコードを書きたいということです。インターフェイスには実装の詳細が含まれていないため、コードは疎結合になります。

実装の詳細は時間とともに変更できますが、やり取りしているのは実装ではなくインターフェースを使用するため、残りのコードには影響しません。あなたは実装がどのようなものであるかを気にしません...あなたが知っているのは、インターフェースを実装するクラスがあることだけです。


3

したがって、これを正しく行うために、インターフェイスの利点は、メソッドの呼び出しを特定のクラスから分離できることです。代わりに、インターフェイスのインスタンスを作成します。実装は、そのインターフェイスを実装するクラスから選択します。したがって、機能は似ているがわずかに異なる多くのクラスを持つことができ、場合によっては(インターフェイスの意図に関連するケース)、どのオブジェクトであっても気にしない。

たとえば、私は移動インターフェースを持つことができます。何かを「移動」するメソッドと、移動インターフェースを実装するオブジェクト(Person、Car、Cat)を渡して、移動するように指示できます。メソッドがなければ、クラスのタイプはすべてわかります。


3

プラグインで拡張できる「Zebra」という製品があるとします。いくつかのディレクトリでDLLを検索してプラグインを見つけます。これらのDLLをすべてロードし、リフレクションを使用して、実装するクラスを見つけます。IZebraPlugin、そのインターフェースのメソッドを呼び出してプラグインと通信します。

これにより、特定のプラグインクラスから完全に独立します。クラスが何であるかは関係ありません。それらがインターフェース仕様を満たしていることのみを気にします。

インターフェイスは、このような拡張性のポイントを定義する方法です。インターフェイスと通信するコードは、より疎結合です。実際、他の特定のコードとはまったく結合されていません。何年も後に元の開発者に会ったことがない人によって書かれたプラグインと相互運用できます。

代わりに、仮想関数を含む基本クラスを使用できます。すべてのプラグインは基本クラスから派生します。ただし、クラスは基本クラスを1つしか持つことができないため、これははるかに制限されます。


3

C ++の説明。

インターフェイスをクラスのパブリックメソッドと考えてください。

次に、独自の関数を実行するために、これらのパブリックメソッドに「依存する」テンプレートを作成できます(クラスのパブリックインターフェイスで定義された関数呼び出しを行います)。このテンプレートがVectorクラスのようなコンテナであり、それが依存するインターフェースが検索アルゴリズムであるとしましょう。

関数/インターフェイスVectorを定義するアルゴリズムクラスは、「元の応答で誰かが説明したように」「契約」を満たす呼び出しを行います。アルゴリズムは、同じ基本クラスである必要さえありません。唯一の要件は、Vectorが依存する関数/メソッド(インターフェース)がアルゴリズムで定義されていることです。

これらすべてのポイントは、Vectorが依存するインターフェース(バブル検索、順次検索、クイック検索)を提供する限り、さまざまな検索アルゴリズム/クラスを提供できることです。

検索アルゴリズムが依存するインターフェース/契約を満たすことで、ベクターと同じ検索アルゴリズムを利用する他のコンテナー(リスト、キュー)を設計することもできます。

これにより、時間を節約できます(OOP原則「コードの再利用」)。作成したすべての新しいオブジェクトに固有のアルゴリズムを何度も何度も作成することができるため、過度に大きくなった継承ツリーの問題を複雑にしすぎることはありません。

物事がどのように機能するかについての「見落とし」について。big-time(少なくともC ++では)、これが標準テンプレートライブラリのフレームワークのほとんどが動作する方法です。

もちろん、継承クラスと抽象クラスを使用すると、インターフェイスへのプログラミングの方法が変わります。原則は同じですが、パブリック関数/メソッドはクラスインターフェイスです。

これは大きなトピックであり、デザインパターンの基本原則の1つです。


3

Javaでは、これらの具象クラスはすべてCharSequenceインターフェースを実装します。

CharBuffer、String、StringBuffer、StringBuilder

これらの具象クラスにはObject以外の共通の親クラスがないため、それぞれが文字の配列と関係がある、それを表す、または操作すること以外は、それらを関連付けるものはありません。たとえば、Stringオブジェクトがインスタンス化されると、Stringの文字は変更できませんが、StringBufferまたはStringBuilderの文字は編集できます。

しかし、これらのクラスはそれぞれ、CharSequenceインターフェースメソッドを適切に実装できます。

char charAt(int index)
int length()
CharSequence subSequence(int start, int end)
String toString()

場合によっては、Stringを受け入れるために使用されていたJavaクラスライブラリクラスが、CharSequenceインターフェースを受け入れるように改訂されました。そのため、StringBuilderのインスタンスがある場合、Stringオブジェクトを抽出する(つまり、新しいオブジェクトインスタンスをインスタンス化する)代わりに、CharSequenceインターフェースを実装するときにStringBuilder自体を渡すだけで済みます。

一部のクラスが実装するAppendableインターフェースは、基礎となる具象クラスオブジェクトインスタンスのインスタンスに文字を追加できるあらゆる状況で、ほぼ同じ種類の利点があります。これらの具象クラスはすべて、Appendableインターフェースを実装します。

BufferedWriter、CharArrayWriter、CharBuffer、FileWriter、FilterWriter、LogStream、OutputStreamWriter、PipedWriter、PrintStream、PrintWriter、StringBuffer、StringBuilder、StringWriter、Writer


それCharSequenceはとても貧弱であるようなそれはあまりにも悪いインターフェースです。Javaと.NETでインターフェースにデフォルトの実装を許可し、人々がボイラープレートコードを最小化する目的でインターフェースを純粋に削らないようにしたいと思います。正当なCharSequence実装があればString、上記の4つの方法のみを使用する機能のほとんどをエミュレートできますが、多くの実装では、これらの機能を他の方法ではるかに効率的に実行できます。残念ながら、の特定の実装CharSequenceがすべてを1つに保持し、char[]多くを実行できる場合でも...
スーパーキャット2014年

... indexOf迅速な操作のように、の特定の実装に慣れていない発信者が、個々の文字を調べるためにCharSequenceを使用charAtする必要があるのではなく、そうするように依頼する方法はありません。
スーパーキャット2014年

3

短編小説:郵便局員は家に帰って家に戻り、送付物が書かれた送付状(手紙、書類、小切手、ギフトカード、申請書、ラブレター)を受け取るように求められます。

カバーがなく、郵便配達員に家に帰ってすべてのものを受け取り、他の人に配達するように依頼すると、郵便配達員は混乱する可能性があります。

ですから、カバーで包む方が(私たちの話ではそれはインターフェースです)、彼はうまく仕事をします。

現在、郵便配達員の仕事はカバーのみを受け取り、配達することです(彼はカバーの中に何が入っているか気にしませんでした)。

interface実際のタイプではなくタイプを作成し、実際のタイプで実装します。

インターフェースを作成することは、コンポーネントが残りのコードに簡単にフィットすることを意味します

例を挙げましょう。

以下のようなAirPlaneインターフェースがあります。

interface Airplane{
    parkPlane();
    servicePlane();
}

次のようなPlanesのControllerクラスにメソッドがあるとします。

parkPlane(Airplane plane)

そして

servicePlane(Airplane plane)

プログラムに実装されています。コードが壊れることはありません。つまり、として引数を受け入れる限り、変更する必要はありませんAirPlane

それは実際のタイプにもかかわらず、すべての飛行機を受け入れますので、flyerhighflyrfighter、など

また、コレクションでは:

List<Airplane> plane; //すべての飛行機を利用します。

次の例はあなたの理解を明確にします。


あなたはそれを実装する戦闘機を持っているので、

public class Fighter implements Airplane {

    public void  parkPlane(){
        // Specific implementations for fighter plane to park
    }
    public void  servicePlane(){
        // Specific implementatoins for fighter plane to service.
    }
}

HighFlyerや他のクラスで同じこと:

public class HighFlyer implements Airplane {

    public void  parkPlane(){
        // Specific implementations for HighFlyer plane to park
    }

    public void  servicePlane(){
        // specific implementatoins for HighFlyer plane to service.
    }
}

ここで、コントローラークラスをAirPlane数回使用していると考えます。

以下のように、コントローラークラスがControlPlaneであるとします。

public Class ControlPlane{ 
 AirPlane plane;
 // so much method with AirPlane reference are used here...
}

新しいAirPlaneタイプのインスタンスを必要な数だけ作成でき、ControlPlaneクラスのコードを変更しないため、魔法がかかります。

インスタンスを追加できます...

JumboJetPlane // implementing AirPlane interface.
AirBus        // implementing AirPlane interface.

以前に作成したタイプのインスタンスも削除できます。


2

インターフェイスはコントラクトに似ており、コントラクト(インターフェイス)で記述されたメソッドを実装クラスで実装する必要があります。Javaは多重継承を提供しないため、「プログラミングによるインターフェース」は多重継承を実現する良い方法です。

すでに他のクラスBを拡張しているクラスAがあるが、そのクラスAも特定のガイドラインに従うか、特定のコントラクトを実装する場合は、「プログラミングからインターフェイスへ」の戦略でそれを行うことができます。


2

Q:-...「インターフェースを実装するクラスを使用できますか?」
A:-はい。

Q:-...「いつそれを行う必要がありますか?」
A:-インターフェースを実装するクラスが必要になるたび。

注: 私たちは、クラスによって実装されていないインタフェースをインスタンス化できなかった - 真。

  • どうして?
  • インターフェイスにはメソッドプロトタイプのみがあり、定義はありません(関数名だけで、ロジックはありません)。

AnIntf anInst = new Aclass();
// これは、 AclassがAnIntf​​を実装している場合にのみ可能です。
// anInstはAclass参照を持ちます。


注: これで、BclassとCclassが同じDintfを実装した場合に何が起こったかを理解できました。

Dintf bInst = new Bclass();  
// now we could call all Dintf functions implemented (defined) in Bclass.

Dintf cInst = new Cclass();  
// now we could call all Dintf functions implemented (defined) in Cclass.

私たちが持っているもの:同じインターフェイスプロトタイプ(インターフェイス内の関数名)、および異なる実装を呼び出します。

参考文献: プロトタイプ-ウィキペディア


1

インターフェイスへのプログラムにより、インターフェイスで定義されたコントラクトの実装をシームレスに変更できます。コントラクトと特定の実装の間の疎結合を可能にします。

IInterface classRef = new ObjectWhatever()

IInterfaceを実装するクラスを使用できますか?いつそれを行う必要がありますか?

良い例については、このSEの質問をご覧ください。

なぜJavaクラスのインターフェースが望ましいのですか?

インターフェイスヒットパフォーマンスの使用

もしそうなら?

はい。1秒未満のわずかなパフォーマンスオーバーヘッドがあります。ただし、インターフェースの実装を動的に変更する必要があるアプリケーションの場合は、パフォーマンスへの影響を心配する必要はありません。

2ビットのコードを維持することなく、どうすればそれを回避できますか?

アプリケーションで必要な場合は、インターフェースの複数の実装を避けようとしないでください。1つの特定の実装とインターフェイスが緊密に結合されていない場合、1つの実装を他の実装に変更するためにパッチを展開する必要がある場合があります。

1つの良い使用例:戦略パターンの実装:

戦略パターンの実例


1

インターフェイスへのプログラムはGOFの本からの用語です。私はそれがjavaインターフェースに関係しているのではなく、実際のインターフェースに直接関係があるとは直接言いません。クリーンなレイヤー分離を実現するには、システム間にいくつかの分離を作成する必要があります。たとえば、使用したい具体的なデータベースがあったとして、「データベースにプログラムする」ことはなく、「ストレージインターフェイスにプログラムする」としましょう。同様に、「Webサービスにプログラムする」ことは決してなく、「クライアントインターフェース」にプログラムすることになります。これは、物事を簡単に交換できるようにするためです。

私はこれらのルールが私を助けると思います:

。オブジェクトのタイプが複数ある場合は、Javaインターフェースを使用します。オブジェクトが1つしかない場合は、ポイントが表示されません。いくつかのアイデアの少なくとも2つの具体的な実装がある場合は、Javaインターフェイスを使用します。

。上で述べたように、外部システム(ストレージシステム)から独自のシステム(ローカルDB)にデカップリングしたい場合は、インターフェイスも使用します。

それらをいつ使用するかを検討する方法が2つあることに注意してください。お役に立てれば。


0

また、ここでは多くの良い説明的な答えが見られるので、この方法を使用したときに気付いた追加情報を含めて、ここで私の見解を述べたいと思います。

ユニットテスト

過去2年間、趣味のプロジェクトを作成しており、そのための単体テストは作成していません。約5万行を書き込んだ後、単体テストを書くことが本当に必要であることを知りました。私はインターフェースを使用しませんでした(または非常に控えめに)...そして、最初の単体テストを行ったとき、それが複雑であることがわかりました。どうして?

たくさんのクラスインスタンスを作成する必要があったので、クラス変数やパラメータとして入力に使用しました。したがって、テストは統合テストに似ています(すべてが結合されているため、クラスの完全な「フレームワーク」を作成する必要があります)。

インターフェイスへの恐怖 それで私はインターフェイスを使うことにしました。私の恐れは、すべての機能をあらゆる場所(すべての使用済みクラス)に何度も実装する必要があることでした。ある意味これは本当ですが、継承を使用することで大幅に削減できます。

インターフェースと継承 の組み合わせ組み合わせて使うのはとてもいいと思いました。私は非常に簡単な例を挙げます。

public interface IPricable
{
    int Price { get; }
}

public interface ICar : IPricable

public abstract class Article
{
    public int Price { get { return ... } }
}

public class Car : Article, ICar
{
    // Price does not need to be defined here
}

このようにコードをコピーする必要はありませんが、車をインターフェイス(ICar)として使用できるという利点があります。


0

まず、いくつかの定義から始めましょう。

インターフェイス n。オブジェクトの操作によって定義されたすべての署名のセットは、オブジェクトへのインターフェースと呼ばれます

nと入力します。特定のインターフェース

簡単な例インタフェース上で定義した通りのようなすべてのPDOオブジェクトのメソッドになりquery()commit()close()全体としてではなく別々など。これらのメソッド、つまりそのインターフェースは、オブジェクトに送信できるメッセージ、リクエストの完全なセットを定義します。

上記で定義されたタイプは、特定のインターフェースです。私は証明するために作られたアップ形状のインターフェースを使用します:draw()getArea()getPerimeter()など。

オブジェクトは、データベースのタイプであれば、我々はそれがメッセージ/データベースインタフェースの要求を受け入れることを意味し、query()commit()など。オブジェクトは、多くの種類があります。インターフェイスを実装している限り、データベースオブジェクトをシェイプタイプにすることができます。その場合、サブタイプになります

多くのオブジェクトは、多くの異なるインターフェース/タイプであり、そのインターフェースを異なる方法で実装できます。これにより、オブジェクトを置き換えることができ、使用するオブジェクトを選択できます。ポリモーフィズムとも呼ばれます。

クライアントはインターフェースのみを認識し、実装は認識しません。

だから、インターフェイスへの本質的プログラミングでは、次のような抽象クラスのいくつかの種類を作る伴うだろうShape唯一つまり、指定されたインターフェイスでdraw()getCoordinates()getArea()など。そして、別の具体的なクラスは、Circleクラス、広場クラス、トライアングルクラスとしてこれらのインタフェースを実装しています。したがって、実装ではなくインターフェイスにプログラムします。


0

「インターフェイスするプログラム」とは、ハードコードを正しく提供しないことを意味します。つまり、以前の機能を損なうことなくコードを拡張する必要があります。以前のコードを編集するのではなく、単なる拡張機能です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.