インターフェース—要点は?


269

インターフェイスの理由は本当に私を逃れています。私が理解していることから、C#には存在しない(または、そう言われたように)存在しない多重継承の回避策のようなものです。

私が見るすべては、あなたはいくつかのメンバーと関数を事前に定義し、それらはクラスで再び再定義される必要があるということです。したがって、インターフェースが冗長になります。それは構文のように感じます…まあ、私にはジャンクです(悪意はありません。役に立たないもののようにジャンク)。

スタックオーバーフローの別のC#インターフェイススレッドから取得した以下の例では、インターフェイスではなくピザと呼ばれる基本クラスを作成します。

簡単な例(別のスタックオーバーフローの貢献から取得)

public interface IPizza
{
    public void Order();
}

public class PepperoniPizza : IPizza
{
    public void Order()
    {
        //Order Pepperoni pizza
    }
}

public class HawaiiPizza : IPizza
{
    public void Order()
    {
        //Order HawaiiPizza
    }
}

ここでこの質問の重複がSOにあるように感じますが、それらはすべてインターフェースの契約部分を説明しているだけなので、適用されるかどうかはわかりません。
ラッセV.カールセン

7
素敵で整頓されたユーザーになるために、何かを投稿する前に、まずさまざまなフォーラムで自分の答えを探す傾向があります。残念ながら、それらのほとんどは後の段階から始まり、残りは役に立ちませんでした。私はすでに基本的な「なぜそれをするのか」と苦労していました。不必要に複雑にしすぎたように思えました。ところで 非常に迅速な回答をありがとうございます。最初にそれらすべてを消化する必要がありますが、今はそれらのポイントについてかなり良い考えを持っていると思います。いつも違う角度から見ているようです。ご協力ありがとうございます。
Nebelhom 2011


1
また、struct型の場合と同様に、インターフェイスは継承の確立に役立ちます。
ja72 2013年

3
うーん、OPは「インターフェイスは、C#には存在しない、存在しない多重継承の回避策のようなものです(それ以外は、引用された教科書ピザの例では)と尋ねていました。インターフェースの代わりに基本クラスを使用してください。」そして、ほとんどの回答は、(抽象的な)基本クラスによって実装できる例を提供するか、マルチ継承シナリオにインターフェースがどのように必要かを示す例を提供しました。これらの答えはすべて良いですが、OPがすでに知っていることを繰り返すだけではありませんか?OPが例なしで答えを選んだのも不思議ではありません。LOL
RayLuo 2017

回答:


176

重要なのは、インターフェースが契約を表すということです。実装クラスに必要な一連のパブリックメソッド。技術的には、インターフェースは構文、つまり、そこにあるメソッド、それらが取得する引数、およびそれらが返すもののみを管理します。通常、それらはセマンティクスもカプセル化しますが、それはドキュメントによってのみです。

次に、インターフェイスのさまざまな実装を用意し、自由に交換できます。あなたの例では、すべてのピザインスタンスはなので、未知のピザタイプのインスタンスを処理IPizzaするIPizza場所ならどこでも使用できます。メソッドIPizzaがあるため、型が継承するインスタンスはすべて、順序付け可能であることが保証されていますOrder()

Pythonは静的に型付けされていないため、実行時に型が保持され、検索されます。したがってOrder()、任意のオブジェクトのメソッドを呼び出すことができます。オブジェクトにそのようなメソッドがあり、おそらく肩をすくめて、そうでない場合は「Meh。」と言う限り、ランタイムは満足です。C#ではそうではありません。コンパイラーは正しい呼び出しを行う責任があります。それがランダムである場合object、コンパイラーは実行時のインスタンスがそのメソッドを持つかどうかまだわかりません。コンパイラの観点からは、検証できないため無効です。(リフレクションやdynamicキーワードを使用してそのようなことを行うことができますが、それは今のところ少し進んでいると思います。)

また、通常の意味でのインターフェースは必ずしもC#interfaceである必要はなく、抽象クラスでも通常のクラスでもかまいません(すべてのサブクラスが共通のコードを共有する必要がある場合に便利です–ほとんどの場合)。ただし、interface十分です)。


1
+1。ただし、インターフェイス(コントラクトの意味で)は、抽象クラスまたは通常のクラスであるとは言えません。
Groo、2011

3
数分でインターフェースを理解することは期待できないと付け加えておきます。何年にもわたってオブジェクト指向プログラミングの経験がない場合は、インターフェースを理解するのは合理的ではないと思います。書籍へのリンクをいくつか追加できます。私はお勧めします.NETの依存性注入は、穏やかな導入だけでなく、実際のところ、ラビットホールの深さです。
knut

2
ああ、DIの手がかりがないという問題があります。しかし、私は質問者の主な問題は、Pythonでそれがすべて機能しないときにそれらが必要とされる理由であったと思います。それが私の回答の最大のパラグラフが提供しようとしたものです。ここでインターフェースを使用するすべてのパターンと実践を掘り下げる必要はないと思います。
ジョーイ

1
さて、あなたの質問は、「動的に型付けされた言語の方がプログラミングしやすいのに、なぜ静的に型付けされた言語を使用するのですか?」です。私はその質問に答える専門家ではありませんが、パフォーマンスが決定的な問題であると言ってもいいでしょう。Pythonオブジェクトへの呼び出しが行われると、プロセスは実行時にそのオブジェクトに「Order」と呼ばれるメソッドがあるかどうかを決定する必要がありますが、C#オブジェクトへの呼び出しを行うと、そのメソッドを実装することがすでに確立されており、そのようなアドレスに電話をかけることができます。
Boluc Papuccuoglu 2013年

3
@BolucPapuccuoglu:それ以上に、静的に型付けされたオブジェクトで、foo実装がわかっているIWidget場合は、への呼び出しを見たプログラマfoo.woozle()は、ドキュメントを見て、そのメソッドが何をすIWidgetべきかを知ることができます。プログラマーは、実際の実装のコードがどこから来るのかを知る方法がないかもしれませんが、IWidgetインターフェース規約を遵守するすべての型は、その規約とfoo一貫した方法で実装されます。対照的に、動的言語では、何foo.woozle()を意味するべきかについて明確な参照点はありません。
スーパーキャット2015

440

インターフェースがどのように役立つかを明確な言葉で実際に説明した人はいないので、試してみます(そしてShamimの答えから少しアイデアを盗みます)。

ピザの注文サービスについて考えてみましょう。複数のタイプのピザを持つことができ、各ピザの共通のアクションはシステムで注文を準備しています。各ピザは準備する必要がありますが、各ピザは異なる方法で準備されます。たとえば、クラストピザの注文時に、システムは特定の食材がレストランで利用可能であることを確認し、深皿のピザには不要な食材を脇に置いておく必要があります。

これをコードで書くとき、技術的には単に行うことができます

public class Pizza()
{
    public void Prepare(PizzaType tp)
    {
        switch (tp)
        {
            case PizzaType.StuffedCrust:
                // prepare stuffed crust ingredients in system
                break;

            case PizzaType.DeepDish:
                // prepare deep dish ingredients in system
                break;

            //.... etc.
        }
    }
}

ただし、ディープディッシュピザ(C#用語)Prepare()では、クラストの詰め物とは異なるプロパティをメソッドに設定する必要がある場合があるため、オプションのプロパティが多くなり、クラスが適切にスケーリングされません(新しいピザの種類)。

これを解決する適切な方法は、インターフェイスを使用することです。インターフェースはすべてのピザを準備できることを宣言しますが、各ピザは異なる方法で準備できます。したがって、次のインターフェイスがある場合:

public interface IPizza
{
    void Prepare();
}

public class StuffedCrustPizza : IPizza
{
    public void Prepare()
    {
        // Set settings in system for stuffed crust preparations
    }
}

public class DeepDishPizza : IPizza
{
    public void Prepare()
    {
        // Set settings in system for deep dish preparations
    }
}

これで、注文処理コードは、食材を処理するために注文されたピザの種類を正確に知る必要がなくなりました。それだけです:

public PreparePizzas(IList<IPizza> pizzas)
{
    foreach (IPizza pizza in pizzas)
        pizza.Prepare();
}

ピザの種類によって準備が異なりますが、コードのこの部分では、処理するピザの種類を気にする必要はありません。ピザのために呼び出されていることがわかっているだけなので、を呼び出すPrepareたびに、各ピザが自動的に正しく準備されますコレクションに複数の種類のピザがある場合でも、その種類に基づいています。


11
+1、インターフェースの使用を示すためにリストを使用する例が好きでした。
danielunderwood 2013

21
良い答えですが、このクラスを修正して、この場合のインターフェースが抽象クラスを使用するよりも優れている理由を明確にしてください(このような単純な例では、抽象クラスは実際に優れているのではないでしょうか?)
Jez

3
これが最良の答えです。少しタイプミスがあるか、コードをコピーして貼り付けるだけかもしれません。C#インターフェイスでは、などのアクセス修飾子を宣言しませんpublic。したがって、インターフェースの内部は、ちょうどvoid Prepare();
Dush

26
これが質問にどのように答えるかはわかりません。この例は、基本クラスと抽象メソッドを使用して同じように簡単に実行できた可能性があります。これは、元の質問が指摘したとおりです。
Jamie Kitson

4
たくさんのインターフェースが必要になるまで、インターフェースは本当にうまくいきません。抽象クラスであるかどうかに関係なく、単一のクラスからのみ継承できますが、単一のクラスで好きなだけ多くの異なるインターフェイスを実装できます。突然、ドアフレームにドアが必要なくなりました。IOpenableが行うことは何でも、ドア、ウィンドウ、レターボックスなど、あなたがそれに名前を付けます。
mtnielsen 2018年

123

私にとって、これらのポイントは、コードをより簡単に/より速く記述できるようにするためのものとして見るのをやめたときにはじめて明らかになりました。これはその目的ではありません。彼らは多くの用途があります:

(これの使い方を視覚化することは非常に簡単ではないので、これはピザのアナロジーを失うでしょう)

画面上で簡単なゲームを作っているとしましょう。それはあなたが相互作用する生き物を持っているでしょう。

A:フロントエンドとバックエンドの実装間の疎結合を導入することにより、将来的にコードを維持しやすくすることができます。

トロルしか存在しないので、これを最初に書くことができます:

// This is our back-end implementation of a troll
class Troll
{
    void Walk(int distance)
    {
        //Implementation here
    }
}

フロントエンド:

function SpawnCreature()
{
    Troll aTroll = new Troll();

    aTroll.Walk(1);
}

2週間後、マーケティングはOrcsもTwitterで読むため、Orcも必要だと判断するため、次のようなことを行う必要があります。

class Orc
{
    void Walk(int distance)
    {
        //Implementation (orcs are faster than trolls)
    }
}

フロントエンド:

void SpawnCreature(creatureType)
{
    switch(creatureType)
    {
         case Orc:

           Orc anOrc = new Orc();
           anORc.Walk();

          case Troll:

            Troll aTroll = new Troll();
             aTroll.Walk();
    }
}

そして、これがいかに厄介になり始めるかを見ることができます。ここでインターフェースを使用して、フロントエンドを1回記述して(重要なビットはここで)テストし、必要に応じてさらにバックエンドアイテムを接続することができます。

interface ICreature
{
    void Walk(int distance)
}

public class Troll : ICreature
public class Orc : ICreature 

//etc

フロントエンドは次のとおりです。

void SpawnCreature(creatureType)
{
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    creature.Walk();
}

フロントエンドは、インターフェイスICreatureのみを対象としています。トロールやオークの内部実装については気にせず、ICreatureを実装しているという事実についてのみ気にします。

この観点からこれを見るときに注意すべき重要な点は、抽象クリーチャークラスを簡単に使用することもでき、この観点から、これは同じ効果を持つということです。

そして、作成物をファクトリーに抽出することができます:

public class CreatureFactory {

 public ICreature GetCreature(creatureType)
 {
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    return creature;
  }
}

そして、フロントエンドは次のようになります。

CreatureFactory _factory;

void SpawnCreature(creatureType)
{
    ICreature creature = _factory.GetCreature(creatureType);

    creature.Walk();
}

フロントエンドはTrollとOrcが実装されているライブラリへの参照さえも持つ必要がなくなりました(ファクトリが別のライブラリにある場合)-それらについてまったく何も知る必要はありません。

B:一部のクリーチャーだけが他の同種のデータ構造持つ機能を持っているとしましょう。

interface ICanTurnToStone
{
   void TurnToStone();
}

public class Troll: ICreature, ICanTurnToStone

フロントエンドは次のようになります。

void SpawnCreatureInSunlight(creatureType)
{
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    creature.Walk();

    if (creature is ICanTurnToStone)
    {
       (ICanTurnToStone)creature.TurnToStone();
    }
}

C:依存関係注入の使用法

ほとんどの依存関係注入フレームワークは、フロントエンドコードとバックエンドの実装の間の結合が非常に緩い場合に、より簡単に処理できます。上記のファクトリーの例を取り、ファクトリーにインターフェースを実装させる場合:

public interface ICreatureFactory {
     ICreature GetCreature(string creatureType);
}

次に、フロントエンドはこれを(通常は)コンストラクターを介して(たとえばMVC APIコントローラーに)注入できます。

public class CreatureController : Controller {

   private readonly ICreatureFactory _factory;

   public CreatureController(ICreatureFactory factory) {
     _factory = factory;
   }

   public HttpResponseMessage TurnToStone(string creatureType) {

       ICreature creature = _factory.GetCreature(creatureType);

       creature.TurnToStone();

       return Request.CreateResponse(HttpStatusCode.OK);
   }
}

DIフレームワーク(NinjectやAutofacなど)を使用すると、実行時にコンストラクターでICreatureFactoryが必要なときにCreatureFactoryのインスタンスが作成されるようにそれらを設定できます。これにより、コードが美しくシンプルになります。

また、コントローラーの単体テストを作成するときに、モック化されたICreatureFactoryを提供でき(たとえば、具体的な実装でDBアクセスが必要な場合、単体テストをそれに依存させたくない場合)、コントローラーのコードを簡単にテストできます。

D:他にも用途があります。たとえば、「レガシー」の理由で構造が適切でない2つのプロジェクトAとBがあり、AはBを参照しています。

次に、すでにAにあるメソッドを呼び出す必要がある機能をBで見つけます。循環参照を取得するため、具体的な実装を使用してそれを行うことはできません。

Aのクラスが実装するインターフェイスをBで宣言できます。Bのメソッドには、具象オブジェクトがAの型であっても、問題なくインターフェースを実装するクラスのインスタンスを渡すことができます。


6
あなたが何であったかを言おうとしていた答えを見つけたときにそれは面倒ではありませんが、それははるかに優れています-stackoverflow.com/a/93998/117215
Paddy

18
良いニュースは死んだページになり、あなたのページはそうではありません:)。良い例え!
Sealer_05 2014年

1
あなたのCの議論は私を少し失いました。しかし、私はあなたのAとBの議論が好きです。どちらも本質的に、インターフェイスを使用して複数のクラスに共通の種類の機能を提供する方法を説明しているからです。私にとってまだあいまいなインターフェイスの領域は、疎結合のためにインターフェイスがどのように使用されるかです。多分それはあなたのCの議論が扱っていたものですか?もしそうなら、私はより詳細な例が必要だと思います:)
user2075599 2017年

1
あなたの例では、ICreatureはTrollとOrcの抽象基本クラス(Creature?)になるのに適しているようです。その後、クリーチャー間の一般的なロジックをそこで実装できます。つまり、ICreatureインターフェイスはまったく必要ありません...
Skarsnik

1
@Skarsnik-かなり、これはこのメモについてです:「この観点からこれを見るときに注意すべき重要な点は、抽象クリーチャークラスを簡単に使用することもでき、この観点から、これは同じです。効果。"
Paddy

33

ここにあなたの例を再説明します:

public interface IFood // not Pizza
{
    public void Prepare();

}

public class Pizza : IFood
{
    public void Prepare() // Not order for explanations sake
    {
        //Prepare Pizza
    }
}

public class Burger : IFood
{
    public void Prepare()
    {
        //Prepare Burger
    }
}

27

上記の例はあまり意味がありません。クラスを使用して上記のすべての例を実行できます(コントラクトとしてのみ動作させたい場合は、抽象クラス):

public abstract class Food {
    public abstract void Prepare();
}

public class Pizza : Food  {
    public override void Prepare() { /* Prepare pizza */ }
}

public class Burger : Food  {
    public override void Prepare() { /* Prepare Burger */ }
}

インターフェースと同じ動作になります。を作成し、List<Food>どのクラスが上位にあるかを認識せずにそれを繰り返すことができます。

より適切な例は、多重継承です。

public abstract class MenuItem {
    public string Name { get; set; }
    public abstract void BringToTable();
}

// Notice Soda only inherits from MenuItem
public class Soda : MenuItem {
    public override void BringToTable() { /* Bring soda to table */ }
}


// All food needs to be cooked (real food) so we add this
// feature to all food menu items
public interface IFood {
    void Cook();
}

public class Pizza : MenuItem, IFood {
    public override void BringToTable() { /* Bring pizza to table */ }
    public void Cook() { /* Cook Pizza */ }
}

public class Burger : MenuItem, IFood {
    public override void BringToTable() { /* Bring burger to table */ }
    public void Cook() { /* Cook Burger */ }
}

次に、それらすべてをそのまま使用でき、MenuItem各メソッド呼び出しの処理方法を気にしません。

public class Waiter {
    public void TakeOrder(IEnumerable<MenuItem> order) 
    {
        // Cook first
        // (all except soda because soda is not IFood)
        foreach (var food in order.OfType<IFood>())
            food.Cook();

        // Bring them all to the table
        // (everything, including soda, pizza and burger because they're all menu items)
        foreach (var menuItem in order)
            menuItem.BringToTable();
    }
}

2
「インターフェイスの代わりに抽象クラスを使用しないのはなぜですか?」という質問に実際に答える回答を見つけるために、ここまで下にスクロールする必要がありました。実際には、c#の多重継承の欠如のみを処理するようです。
SyntaxRules '19

2
はい、これは私にとって最良の説明です。インターフェースが抽象クラスよりも有用である理由を明確に説明している唯一のもの。(つまり、ピザはMenuItemであり、Foodでもありますが、抽象クラスの場合、どちらか一方しか使用できませんが、両方は使用できません)
Maxter

25

類推による簡単な説明

解決すべき問題:ポリモーフィズムの目的は何ですか?

類推:つまり、私は建設現場の監督です。

商人は工事現場をいつも歩いています。誰がそれらのドアを通り抜けるのか分かりません。しかし、私は基本的に彼らに何をすべきかを伝えます。

  1. それが大工の場合、私は言う:木製の足場を構築します。
  2. 配管工の場合は、「パイプを設置する」と言います。
  3. 電気技師なら、「ケーブルを引き出して、光ファイバーケーブルに交換する」と言います。

上記のアプローチの問題は、私がしなければならないことです。つまり、特定の取引に関するすべてを知っている必要があります。このアプローチにはコスト/メリットがあります。

何をすべきかを知ることの意味:

  • これは、カーペンターのコードが:BuildScaffolding()からBuildScaffold()(つまり、わずかな名前の変更)に変更された場合、呼び出し元のクラス(つまり、Forepersonクラス)も変更する必要があることを意味します- (基本的に)ではなく、コードに2つの変更を加える必要があります。) 一つだけです。ポリモーフィズムを使用すると、(基本的に)同じ結果を達成するために1つの変更を行うだけで済みます。

  • 次に、常に尋ねる必要はありません。あなたは誰ですか?わかりました、これは...あなたは誰ですか?わかりました。多態性-そのコードを乾燥させ、特定の状況で非常に効果的です。

  • ポリモーフィズムを使用すると、既存のコードを変更することなく、職人のクラスを簡単に追加できます。(つまり、SOLID設計原理の2番目:開閉原理)。

ソリューション

誰がドアを歩いても、「Work()」と言って、彼らが専門とする敬意の仕事をするシナリオを想像してみてください。配管工はパイプを扱い、電気技師は電線を扱います。

このアプローチの利点は、次のとおりです。(i)誰がそのドアを通り抜けているかを正確に知る必要はありません。知っておく必要があるのは、彼らが一種のトラディエであり、彼らが仕事をすることができることです。 (ii)その特定の取引について何も知る必要はありません。トラディはそれを処理します。

これの代わりに:

If(electrician) then  electrician.FixCablesAndElectricity() 

if(plumber) then plumber.IncreaseWaterPressureAndFixLeaks() 

私はこのようなことができます:

ITradesman tradie = Tradesman.Factory(); // in reality i know it's a plumber, but in the real world you won't know who's on the other side of the tradie assignment.

tradie.Work(); // and then tradie will do the work of a plumber, or electrician etc. depending on what type of tradesman he is. The foreman doesn't need to know anything, apart from telling the anonymous tradie to get to Work()!!

メリットは何ですか?

利点は、大工などの特定の仕事の要件が変更された場合、フォアパーソンが自分のコードを変更する必要がないことです-彼は知る必要も、気にする必要もありません。重要なのは、大工がWork()の意味を知っているということだけです。第2に、新しいタイプの建設作業員が現場に来た場合、職長は取引について何も知る必要はありません。建設作業員(例:溶接機、グレイザー、タイル工など)ができることなら、すべての職長が気にする必要があります。 Work()を実行します。


図解された問題と解決策(インターフェースあり/なし):

インターフェースなし(例1):

例1:インターフェースなし

インターフェースなし(例2):

例2:インターフェースなし

インターフェースあり:

例3:インターフェースを使用する利点

概要

インターフェイスを使用すると、自分が誰であるか、または何ができるかについての詳細を知らなくても、割り当てられている作業をその人に実行させることができます。これにより、既存のコードを変更せずに(トレードの)新しいタイプを簡単に追加できます(技術的には、ごくわずかに変更します)。これが、より機能的なプログラミング手法と比較したOOPアプローチの真のメリットです。

上記のいずれかを理解していない場合、またはそれが明確でない場合は、コメントで質問してください。


4
加えて、パーカーで仕事に取り掛かる人のための1つ。
Yusha

11

Pythonで使用できるようにダックタイピングがない場合、C#は抽象化を提供するためにインターフェイスに依存します。クラスの依存関係がすべて具象型である場合、他の型を渡すことはできません。インターフェースを使用して、インターフェースを実装する任意の型を渡すことができます。


3
+1そうです、ダックタイピングをしているのであれば、インターフェースは必要ありません。しかし、それらはより強い型安全を強制します。
Groo、2011

11

たとえば、順序を処理する抽象クラスを使用する必要があり、ピザはピザのタイプをオーバーライドするだけなので、ピザの例は不適切です。

共有プロパティがあるが、クラスが別の場所から継承する場合、または使用できる一般的なコードがない場合は、インターフェイスを使用します。例えば、これは処分できる中古品ですIDisposableことはわかっていますが、廃棄されるとどうなるかわかりません。

インターフェースは、オブジェクトが実行できるいくつかのこと、どのパラメーター、およびどの戻り値型を期待するかを通知する単なるコントラクトです。


10

基本クラスを制御または所有していない場合を考えてみます。

たとえば、.NET for Winformsのビジュアルコントロールは、すべて.NETフレームワークで完全に定義されている基本クラスControlから継承します。

カスタムコントロールを作成しているとします。新しいボタン、テキストボックス、リストビュー、グリッドなどを作成し、すべてのコントロールに固有の特定の機能を持たせたいとします。

たとえば、テーマを処理する一般的な方法や、ローカリゼーションを処理する一般的な方法が必要になる場合があります。

この場合、「基本クラスを作成する」ことはできません。その場合、コントロールに関連するすべてのものを再実装する必要があるためです。

代わりに、Button、TextBox、ListView、GridViewなどから派生し、コードを追加します。

しかし、これは問題を引き起こします。どのコントロールが「自分のもの」であるかをどのように特定できるか、「自分のフォーム上のすべてのコントロールについて、テーマをXに設定する」というコードをどのように構築できるでしょうか。

インターフェイスを入力します。

インターフェイスは、オブジェクトを調べて、オブジェクトが特定の規約に準拠していることを確認する方法です。

「YourButton」を作成し、Buttonから派生し、必要なすべてのインターフェースのサポートを追加します。

これにより、次のようなコードを記述できます。

foreach (Control ctrl in Controls)
{
    if (ctrl is IMyThemableControl)
        ((IMyThemableControl)ctrl).SetTheme(newTheme);
}

これはインターフェイスなしでは不可能です。代わりに、次のようなコードを記述する必要があります。

foreach (Control ctrl in Controls)
{
    if (ctrl is MyThemableButton)
        ((MyThemableButton)ctrl).SetTheme(newTheme);
    else if (ctrl is MyThemableTextBox)
        ((MyThemableTextBox)ctrl).SetTheme(newTheme);
    else if (ctrl is MyThemableGridView)
        ((MyThemableGridView)ctrl).SetTheme(newTheme);
    else ....
}

はい、知っています。「is」を使用してキャストしないでください。
ラッセV.カールセン

4
ため息は知っているが、それはここでは偶発的だ。
Lasse V. Karlsen、2011

7

この場合、Pizzaの基本クラスを定義して継承することができます(おそらくそうするでしょう)。ただし、インターフェイスを使用して他の方法では達成できないことを実行できる理由は2つあります。

  1. クラスは複数のインターフェースを実装できます。それはクラスが持つべき機能を定義するだけです。さまざまなインターフェースを実装することは、クラスがさまざまな場所で複数の機能を実行できることを意味します。

  2. インターフェイスは、クラスや呼び出し元よりも広いスコープで定義できます。つまり、機能を分離し、プロジェクトの依存関係を分離し、機能を1つのプロジェクトまたはクラスに保持し、これを別の場所に実装することができます。

2の影響の1つは、使用されているクラスを変更できることです。適切なインターフェースを実装することだけが必要です。




4

図形を描画するAPIに取り組んでいる場合は、DirectXやグラフィックの呼び出し、またはOpenGLを使用することができます。それで、あなたが呼ぶものから私の実装を抽象化するインターフェースを作成します。

したがって、ファクトリメソッドを呼び出しますMyInterface i = MyGraphics.getInstance()。次に、契約を結んでいるので、どのような機能を期待できるかがわかります。MyInterface。だから、あなたが呼び出すことができますi.drawRectanglei.drawCube、あなたは、別のもののライブラリをスワップアウトした場合の機能がサポートされていることを知っています。

依存性注入を使用している場合、XMLファイルで実装をスワップアウトできるため、これはより重要になります。

したがって、一般的な使用のためにエクスポートできる1つの暗号化ライブラリと、アメリカの企業にのみ販売される別の暗号化ライブラリがあり、構成ファイルを変更し、プログラムの残りの部分は変更しないという違いがあります。かわった。

これは、たとえば.NETのコレクションでよく使用されます。たとえば、 List変数を ArrayListとLinkedListのどちらでもかまいません。

インターフェイスにコーディングする限り、開発者は実際の実装を変更でき、プログラムの残りの部分は変更されません。

インターフェース全体をモックアウトできるので、これはユニットテストにも役立ちます。データベースに移動する必要はありませんが、静的データを返すだけのモックアウトされた実装に移動するので、心配することなくメソッドをテストできます。データベースはメンテナンスのためにダウンしているかどうか。


4

インターフェイスは実際には実装クラスが従わなければならないコントラクトであり、実際、私が知っているほとんどすべてのデザインパターンのベースです。

あなたの例では、インターフェイスが作成されます。これは、Pizzaインターフェースを実装することを意味するPizza ISが実装されていることが保証されているためです。

public void Order();

あなたが言及したコードの後、あなたはこのようなものを持つことができます:

public void orderMyPizza(IPizza myPizza) {
//This will always work, because everyone MUST implement order
      myPizza.order();
}

この方法では、ポリモーフィズムを使用していて、オブジェクトがorder()に応答することだけが重要です。


4

このページで「作曲」という単語を検索しましたが、一度も見つかりませんでした。この回答は、前述の回答に加えて非常に多くなります。

オブジェクト指向プロジェクトでインターフェースを使用する決定的な理由の1つは、継承よりも構成を優先できることです。インターフェースを実装することで、実装に適用しているさまざまなアルゴリズムから実装を切り離すことができます。

Derek Banasによるこのすばらしい「デコレータパターン」チュートリアル(おかしなことに、例としてピザも使用しています)は、価値のある図です。

https://www.youtube.com/watch?v=j40kRwSm4VE


2
これが最善の答えではないことに本当にショックを受けます。インターフェースは、そのすべての有用性の中で、構成において最も有用です。
Maciej Sitko

3

インターフェイスは、異なるクラス間の接続を適用するためのものです。たとえば、車と木のクラスがあります。

public class Car { ... }

public class Tree { ... }

両方のクラスに書き込み可能な機能を追加したい。しかし、クラスごとに独自の書き込み方法があります。だからあなたは単に作る。

public class Car : IBurnable
{
public void Burn() { ... }
}

public class Tree : IBurnable
{
public void Burn() { ... }
}

2
このような例で私を悩ませている質問は、次のとおりです。なぜこれが役立つのですか?IBurnable型の引数をメソッドに渡して、IBurnableインターフェイスを持つすべてのクラスを処理できるようになりましたか?私が見つけたピザの例と同じです。これができるのはいいことですが、その利点は私にはわかりません。例を拡張するか(私は今のところ本当に分厚いので)、それともうまくいかない例を挙げていただけますか(もう一度、今は本当に分厚いので)。どうもありがとう。
Nebelhom 2011

1
同意します。インターフェース=「できる」。クラス/抽象Calss = "Is A"
Peter.Wang

3

あなたはそれらを必要とするときにインターフェースを手に入れます:)あなたは例を学ぶことができますが、あなたはAhaが必要です!実際にそれらを取得する効果。

インターフェースが何であるかがわかったので、インターフェースなしでコーディングしてください。遅かれ早かれ、インターフェースの使用が最も自然なことになる問題に遭遇します。


3

インターフェースの最も重要な理由の1つである「デザインパターン」を含む投稿が少ないことに驚きます。これは、コントラクトの使用に関する全体像であり、マシンコードに対する構文の装飾です(正直に言うと、コンパイラはおそらくそれらを単に無視するだけです)、抽象化とインターフェイスは、OOP、人間の理解、および複雑なシステムアーキテクチャにとって極めて重要です。

ピザの例えを拡張して、本格的な3コースの食事としましょう。Prepare()すべての食品カテゴリのコアインターフェイスはまだありますが、コース選択(スターター、メイン、デザート)の抽象宣言と、食品タイプの異なるプロパティ(おいしい/甘い、ベジタリアン/非ベジタリアン、グルテンフリーなど)。

これらの仕様に基づいて、Abstract Factoryパターンを実装してプロセス全体を概念化できますが、インターフェースのみを使用して、基盤のみが具体的であることを確認できます。他のすべては柔軟になるか、またはポリモーフィズムを促進Courseしますが、ICourseインターフェースを実装するクラスの異なるクラス間のカプセル化を維持します。

時間があれば、この完全な例を作成するか、誰かが私のためにこれを拡張できますが、要約すると、C#インターフェイスがこのタイプのシステムを設計するのに最適なツールです。


1
この答えはより多くのポイントに値します!状態パターンなどの設計されたパターンで使用すると、インターフェイスが輝きます。詳細については、plus.google.com / + ZoranHorvat
Alex Nolasco、

3

長方形の形状を持つオブジェクトのインターフェースは次のとおりです。

interface IRectangular
{
    Int32 Width();
    Int32 Height();
}

必要なのは、オブジェクトの幅と高さにアクセスする方法を実装することだけです。

次のようなオブジェクトで機能するメソッドを定義してみましょうIRectangular

static class Utils
{
    public static Int32 Area(IRectangular rect)
    {
        return rect.Width() * rect.Height();
    }
}

これは、長方形のオブジェクトの領域を返します。

SwimmingPool長方形のクラスを実装してみましょう:

class SwimmingPool : IRectangular
{
    int width;
    int height;

    public SwimmingPool(int w, int h)
    { width = w; height = h; }

    public int Width() { return width; }
    public int Height() { return height; }
}

そして、同じくHouse長方形の別のクラス:

class House : IRectangular
{
    int width;
    int height;

    public House(int w, int h)
    { width = w; height = h; }

    public int Width() { return width; }
    public int Height() { return height; }
}

それを前提として、Area家またはプールでメソッドを呼び出すことができます。

var house = new House(2, 3);

var pool = new SwimmingPool(3, 4);

Console.WriteLine(Utils.Area(house));
Console.WriteLine(Utils.Area(pool));

このようにして、クラスは任意の数のインターフェースから動作(静的メソッド)を「継承」できます。


3

インターフェイスは、特定の機能のプロバイダーと対応するコンシューマー間の契約を定義します。コントラクト(インターフェース)から実装を分離します。オブジェクト指向のアーキテクチャとデザインを確認する必要があります。あなたはウィキペディアから始めたいかもしれません:http : //en.wikipedia.org/wiki/Interface_(computing)


3

何 ?

インターフェイスは基本的に、インターフェイスを実装するすべてのクラスが従うべきコントラクトです。それらはクラスのように見えますが、実装はありません。

ではC#インタフェース慣例により名前はあなたがインターフェイスと呼ばれる形状を持つようにしたい場合は「I」そう、あなたのようにそれを宣言し付けることによって定義されますIShapes

なぜですか?

Improves code re-usability

あなたが描きたいとしましょうCircleTriangle. あなたがグループ化することができ、それらが一緒にし、それらを呼び出すShapesと、描画する方法持っているCircleTriangle 明日、あなたがより多くの2を持っていることを決定するかもしれないので、しかし、具体的な実装を持つことになり、悪い考えをShapes RectangleSquare。これらを追加すると、コードの他の部分が壊れる可能性が高くなります。

インターフェイスを使用すると、契約からさまざまな実装を分離できます


ライブシナリオ1日目

円と三角形を描画するアプリを作成するように求められました

interface IShapes
{
   void DrawShape();
   
 }

class Circle : IShapes
{
    
    public void DrawShape()
    {
        Console.WriteLine("Implementation to Draw a Circle");
    }
}

Class Triangle: IShapes
{
     public void DrawShape()
    {
        Console.WriteLine("Implementation to draw a Triangle");
    }
}
static void Main()
{
     List <IShapes> shapes = new List<IShapes>();
        shapes.Add(new Circle());
        shapes.Add(new Triangle());

        foreach(var shape in shapes)
        {
            shape.DrawShape();
        }
}

ライブシナリオ2日目

追加SquareRectangleそれに追加するように求められた場合、あなたがしなければならないことは、リストclass Square: IShapesMain追加してリストに追加することです。shapes.Add(new Square());


6年前の質問に何十回も投票した他の回答が何十もある質問に回答を追加するのはなぜですか?まだ言われていないことはここにはありません。
Jonathon Reinhart

@JonathonReinhart、はい、そう思いましたが、私はこの例とその説明が他の体よりもある体に関連するのに役立つ方法を考えていました。
クリント

2

ここには良い答えがたくさんありますが、少し違う視点から試してみたいと思います。

オブジェクト指向設計のSOLID原則に精通しているかもしれません。要約すれば:

S-単一の責任の原則O-オープン/クローズの原則L-リスコフ置換の原則I-インターフェース分離の原則D-依存関係の逆転の原則

SOLIDの原則に従うと、クリーンで、十分に因数分解され、まとまりがあり、疎結合のコードを生成できます。とすれば:

「依存性管理はあらゆる規模のソフトウェアにおける主要な課題です」(Donald Knuth)

依存関係の管理に役立つものは何でも大きな勝利です。インターフェースと依存関係の逆転の原則は、コードを具象クラスへの依存関係から切り離すのに役立ちます。そのため、コードを動作に関して記述し、推論することができます。実装ではなく。これは、コードをコンポーネントに分割し、コンパイル時ではなく実行時に構成できるようにするのに役立ちます。また、これらのコンポーネントは、コードの残りの部分を変更することなく、非常に簡単にプラグインおよびプラグアウトできます。

インターフェースは、特に、コードがサービスのコレクションにコンポーネント化され、各サービスがインターフェースによって記述される依存関係の逆転の原則で役立ちます。次に、サービスをコンストラクタパラメータとして渡すことにより、実行時にクラスに「注入」できます。ユニットテストを作成し、テスト駆動開発を使用する場合、この手法は本当に重要になります。それを試してみてください!インターフェースを使用して、コードを個別に個別にテストできる管理可能なチャンクに分割する方法をすぐに理解できます。


1

インターフェースの主な目的は、ユーザーとそのインターフェースを実装する他のクラスとの間で契約を結び、コードを分離して拡張性を可能にすることです。


1

これらは本当に素晴らしい例です。

もう1つは、switchステートメントの場合、特定の方法でタスクを実行するたびにメンテナンスや切り替えを行う必要がなくなります。

ピザの例では、ピザを作りたい場合、必要なのはインターフェースだけで、そこから各ピザが独自のロジックを処理します。

これは、カップリングとサイクロマティックの複雑さを軽減するのに役立ちます。ロジックを実装する必要がありますが、全体像を把握する必要が少なくなります。

ピザごとに、そのピザに固有の情報を追跡できます。他のピザだけが知っている必要があるので、他のピザが何を持っているかは重要ではありません。


1

インターフェイスについて考える最も簡単な方法は、継承の意味を認識することです。クラスCCがクラスCを継承する場合、それは次の両方を意味します。

  1. クラスCCは、クラスCのパブリックメンバーまたは保護されたメンバーを、それらが独自のものであるかのように使用できるため、親クラスに存在しないものを実装するだけで済みます。
  2. CCへの参照は、Cへの参照を期待するルーチンまたは変数に渡すか、割り当てることができます。

これらの2つの継承機能は、ある意味で独立しています。継承は両方に同時に適用されますが、最初のものなしで2番目のものを適用することも可能です。オブジェクトが2つ以上の無関係なクラスからメンバーを継承できるようにすることは、1つのタイプのものを複数のタイプで代用できるようにするよりもはるかに複雑であるため、これは便利です。

インターフェイスは抽象基本クラスに似ていますが、重要な違いがあります。基本クラスを継承するオブジェクトは他のクラスを継承できません。対照的に、オブジェクトは、必要なクラスを継承したり、他のインターフェイスを実装したりする能力に影響を与えずに、インターフェイスを実装できます。

これの1つの優れた機能(.NETフレームワークで十分に活用されていないIMHO)は、オブジェクトが実行できることを宣言的に示すことができることです。たとえば、一部のオブジェクトは、(Listで可能なように)インデックスで物事を取得できるデータソースオブジェクトを必要としますが、そこに何も格納する必要はありません。他のルーチンは、(Collection.Addの場合のように)インデックスではなく物事を格納できるデータリポジトリオブジェクトを必要としますが、何も読み取る必要はありません。一部のデータ型はインデックスによるアクセスを許可しますが、書き込みは許可しません。他のユーザーは書き込みを許可しますが、インデックスによるアクセスは許可しません。もちろん、両方を許可するものもあります。

ReadableByIndexとAppendableが関連しない基本クラスである場合、ReadableByIndexを予期するものとAppendableを予期するものの両方に渡すことができる型を定義することは不可能です。ReadableByIndexまたはAppendableを他から派生させることで、これを軽減しようとすることができます。派生クラスは両方の目的でパブリックメンバーを使用可能にする必要がありますが、一部のパブリックメンバーは実際には機能しない可能性があることを警告します。Microsoftのクラスとインターフェースのいくつかはそれを行いますが、それはかなり厄介です。より明確なアプローチは、さまざまな目的のためのインターフェイスを用意し、オブジェクトが実際に実行できることのためにインターフェイスを実装することです。IReadableByIndexインターフェースと別のインターフェースIAppendableがあった場合、


1

インターフェースをデイジーチェーン接続して、さらに別のインターフェースを作成することもできます。複数のインターフェースを実装するこの機能により、開発者は、現在のクラスの機能を変更する必要なく、クラスに機能を追加できるという利点があります(SOLID原則)。

O =「クラスは拡張のために開いているが、変更のために閉じる必要がある」


1

インターフェースの利点/利点は、抽象クラスよりも柔軟性があることです。継承できる抽象クラスは1つだけですが、複数のインターフェースを実装できるため、多くの場所で抽象クラスを継承するシステムへの変更は問題になります。100か所で継承されている場合、変更には100個すべてを変更する必要があります。ただし、インターフェースを使用すると、新しい変更を新しいインターフェースに配置し、そのインターフェースを必要な場所で使用することができます(SOLIDからのインターフェースシーケンス)。さらに、インターフェイスの実装場所がいくつあっても、インターフェイスの例のオブジェクトはメモリ内で1回だけ使用されるため、インターフェイスの方がメモリ使用量は少なくなるようです。


1

インターフェイスは、疎結合の方法で一貫性を駆動するために使用されます。これにより、密結合された抽象クラスとは異なります。そのため、一般的にコントラクトとしても定義されます。インターフェイスを実装するクラスは、「ルール/構文」に準拠していますインターフェースによって定義され、その中に具体的な要素はありません。

以下の図でサポートされている例を示します。

工場には3種類のマシンがあると想像してください。長方形マシン、三角形マシン、多角形マシン。時間は競争が激しく、オペレーターのトレーニングを合理化したい場合。マシンを起動および停止する1つの方法でトレーニングするだけで、緑のスタートボタンと赤のストップボタンがあります。3つの異なるマシンで、3つの異なるタイプのマシンを起動および停止する一貫した方法があります。次に、これらのマシンがクラスであり、クラスにstartメソッドとstopメソッドが必要で、非常に異なる可能性があるこれらのクラス全体の一貫性を推進するつもりですか?答えはインターフェースです。

ここに画像の説明を入力してください

視覚化に役立つ簡単な例ですが、抽象クラスを使用しないのはなぜでしょうか。インターフェイスを使用すると、オブジェクトを直接関連付けたり継承したりする必要がなく、異なるクラス間で一貫性を保つことができます。

public interface IMachine
{
    bool Start();
    bool Stop();
}

public class Car : IMachine
{
    public bool Start()
    {
        Console.WriteLine("Car started");
        return true;
    }

    public bool Stop()
    {
        Console.WriteLine("Car stopped");
        return false;
    }
}

public class Tank : IMachine
{
    public bool Start()
    {
        Console.WriteLine("Tank started");
        return true;
    }

    public bool Stop()
    {
        Console.WriteLine("Tank stopped");
        return false;
    }
}

class Program
{
    static void Main(string[] args)
    {
        var car = new Car();
        car.Start();
        car.Stop();

        var tank = new Tank();
        tank.Start();
        tank.Stop();

    }
}

1
class Program {
    static void Main(string[] args) {
        IMachine machine = new Machine();
        machine.Run();
        Console.ReadKey();
    }

}

class Machine : IMachine {
    private void Run() {
        Console.WriteLine("Running...");
    }
    void IMachine.Run() => Run();
}

interface IMachine
{
    void Run();
}

これを別の視点で説明します。上で示した例に従ってストーリーを作成してみましょう。

Program、Machine、およびIMachineが私たちのストーリーの俳優です。プログラムは実行したいが、その能力はなく、マシンは実行方法を知っている。MachineとIMachineは親友ですが、ProgramはMachineとの会話にはありません。そのため、ProgramとIMachineは取引を行い、IMachineがProgramに(リフレクターのように)Machineを見て実行する方法を指示することを決定しました。

そしてプログラムはIMachineの助けを借りて実行する方法を学びます。

インターフェースは、コミュニケーションと疎結合プロジェクトの開発を提供します。

PS:私は具象クラスのメソッドをプライベートとして持っています。ここでの私の目的は、具体的なクラスのプロパティとメソッドへのアクセスを防ぎ、インターフェースを介してそれらに到達する方法のみを残して、疎結合を達成することです。(私は明示的にインターフェイスのメソッドを定義しました)。


1

私は非常に遅い..(ほぼ9年)ですが、誰かが小さな説明を望んでいるなら、あなたはこれに行くことができます:

簡単に言うと、オブジェクトが実行できること、またはオブジェクトに実装する機能がわかっている場合は、インターフェイスを使用します。例:挿入、更新、削除。

interface ICRUD{
      void InsertData(); // will insert data
      void UpdateData(); // will update data
      void DeleteData(); // will delete data
}

重要な注意:インターフェイスは常にパブリックです。

お役に立てれば。

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