web-dev-qa-db-de.com

C # -Schnittstellen - Was ist der Punkt?

Der Grund für Schnittstellen entgeht mir wirklich. Nach allem, was ich verstehe, ist es eine Art Umgehung für die nicht existierende Multi-Vererbung, die in C # nicht existiert (oder so wurde mir gesagt).

Alles was ich sehe ist, dass Sie einige Elemente und Funktionen vordefinieren, die dann in der Klasse erneut definiert werden müssen. Dadurch wird die Schnittstelle überflüssig. Es fühlt sich einfach an wie syntaktisch ... na ja, Junk für mich (Bitte keine Beleidigung gemeint. Junk wie in nutzlosen Sachen).

In dem unten angegebenen Beispiel aus einem anderen C # -Schnittstellen-Thread bei Stack-Überlauf würde ich einfach eine Basisklasse mit dem Namen Pizza anstelle einer Schnittstelle erstellen.

einfaches Beispiel (aus einem anderen Stack-Überlaufbeitrag)

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
    }
}
226
Nebelhom

Der Punkt ist, dass die Schnittstelle einen Vertrag darstellt. Eine Reihe von öffentlichen Methoden, die jede implementierende Klasse haben muss. Technisch bestimmt die Schnittstelle nur die Syntax, d. H. Welche Methoden vorhanden sind, welche Argumente sie erhalten und was sie zurückgeben. Normalerweise kapseln sie auch Semantik ein, allerdings nur durch Dokumentation.

Sie können dann verschiedene Implementierungen einer Schnittstelle haben und diese nach Belieben austauschen. Da es sich bei jeder Pizza-Instanz um IPizza handelt, können Sie IPizza überall dort verwenden, wo Sie mit einer Instanz eines unbekannten Pizzatyps arbeiten. Jede Instanz, deren Typ von IPizza erbt, ist garantiert bestellbar, da sie eine Order()-Methode hat.

Python ist nicht statisch typisiert. Daher werden Typen zur Laufzeit beibehalten und nachgeschlagen. Sie können also versuchen, eine Order()-Methode für jedes Objekt aufzurufen. Die Laufzeit ist glücklich, solange das Objekt eine solche Methode hat und wahrscheinlich nur mit den Schultern zuckt und »Meh.« Sagt, falls dies nicht der Fall ist. Nicht so in C #. Der Compiler ist für die korrekten Aufrufe verantwortlich. Wenn er nur zufällige object hat, weiß der Compiler noch nicht, ob die Instanz zur Laufzeit über diese Methode verfügt. Aus der Sicht des Compilers ist er ungültig, da er ihn nicht überprüfen kann. (Sie können solche Dinge mit Reflektion oder mit dem Schlüsselwort dynamic tun, aber das ist momentan ein bisschen weit, denke ich.)

Beachten Sie auch, dass ein Interface im üblichen Sinne nicht unbedingt ein C # interface sein muss. Es kann sich auch um eine abstrakte Klasse oder sogar eine normale Klasse handeln (was sich als nützlich erweisen kann, wenn alle Unterklassen gemeinsamen Code benötigen - in den meisten Fällen) In diesen Fällen ist jedoch interface ausreichend.

167
Joey

Niemand hat wirklich in einfachen Worten erklärt, wie Schnittstellen nützlich sind, also werde ich es versuchen (und Shamims Antwort ein wenig stehlen). 

Nehmen wir die Idee eines Pizzabestelldienstes. Sie können mehrere Pizzasorten haben. Eine gemeinsame Aktion für jede Pizza ist die Vorbereitung der Bestellung im System. Jede Pizza muss vorbereitet sein aber jede Pizza wird anders vorbereitet. Wenn zum Beispiel eine gefüllte Krustenpizza bestellt wird, muss das System wahrscheinlich überprüfen, ob bestimmte Zutaten im Restaurant verfügbar sind, und diejenigen beiseite stellen, die nicht für tiefe Gerichtspizzas benötigt werden.

Wenn Sie dies in Code schreiben, können Sie dies technisch tun

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.
        }
    }
}

Bei Pizza mit tiefem Teller (in C # -Begriffen) müssen jedoch andere Eigenschaften in der Prepare()-Methode als bei der gefüllten Kruste festgelegt werden, sodass Sie viele optionale Eigenschaften erhalten und die Klasse nicht gut skaliert wird (was ist, wenn Sie hinzufügen.) neue Pizzasorten).

Der richtige Weg, dieses Problem zu lösen, ist die Verwendung der Schnittstelle. Die Schnittstelle erklärt, dass alle Pizzas zubereitet werden können, aber jede Pizza kann unterschiedlich zubereitet werden. Wenn Sie also folgende Schnittstellen haben:

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
    }
}

Nun muss der Bestellungscode für die Bestellung nicht genau wissen, welche Pizzasorten für die Verarbeitung der Zutaten bestellt wurden. Es hat nur:

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

Auch wenn jede Art von Pizza anders zubereitet wird, muss dieser Teil des Codes sich nicht darum kümmern, mit welcher Art von Pizza wir es zu tun haben. Er weiß nur, dass er Pizza genannt wird, und jeder Aufruf von Prepare wird automatisch jede Pizza vorbereiten korrekt basierend auf seinem Typ, auch wenn die Sammlung mehrere Pizzatypen enthält.

409
KallDrexx

Für mich wurde der Punkt zu diesen Themen erst klar, wenn Sie aufhören, sie als Dinge zu betrachten, um Ihren Code einfacher/schneller zu schreiben - dies ist nicht deren Zweck. Sie haben eine Reihe von Anwendungen:

(Dies wird die Pizza-Analogie verlieren, da es nicht sehr einfach ist, sich die Verwendung davon zu veranschaulichen.)

Angenommen, Sie erstellen ein einfaches Spiel auf dem Bildschirm, und es wird Kreaturen geben, mit denen Sie interagieren.

A: Sie können Ihren Code in Zukunft leichter pflegen, indem Sie eine lose Kopplung zwischen Ihrem Front-End und Ihrer Back-End-Implementierung einführen. 

Sie könnten dies zu Beginn schreiben, da es nur Trolle geben wird:

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

Vorderes Ende:

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

    aTroll.Walk(1);
}

Zwei Wochen später entscheidet das Marketing, dass Sie auch Orks brauchen, da sie auf Twitter darüber gelesen haben. Sie müssten also Folgendes tun:

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

Vorderes Ende:

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

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

          case Troll:

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

Und Sie können sehen, wie dies unordentlich wird. Sie könnten hier eine Schnittstelle verwenden, damit Ihr Frontend einmal geschrieben und (hier das wichtige Bit) getestet wird, und Sie können nach Bedarf weitere Back-End-Elemente anschließen:

interface ICreature
{
    void Walk(int distance)
}

public class Troll : ICreature
public class Orc : ICreature 

//etc

Frontend ist dann:

void SpawnCreature(creatureType)
{
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    creature.Walk();
}

Das Frontend kümmert sich jetzt nur noch um die Schnittstelle ICreature - es kümmert sich nicht um die interne Implementierung eines Trolls oder Orks, sondern nur um die Tatsache, dass sie ICreature implementieren.

Ein wichtiger Punkt, der aus dieser Sicht betrachtet werden muss, ist, dass Sie auch eine abstrakte Kreaturenklasse hätten verwenden können. Aus dieser Perspektive hat dies den Effekt same

Und Sie könnten die Kreation in eine Fabrik extrahieren:

public class CreatureFactory {

 public ICreature GetCreature(creatureType)
 {
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    return creature;
  }
}

Und unser Frontend würde dann zu:

CreatureFactory _factory;

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

    creature.Walk();
}

Das Frontend muss jetzt nicht einmal einen Verweis auf die Bibliothek haben, in der Troll und Orc implementiert sind (vorausgesetzt, die Fabrik befindet sich in einer separaten Bibliothek) - es muss nichts über sie wissen.

B: Angenommen, Sie verfügen über Funktionen, die nur einige Kreaturen in Ihrer ansonsten homogenen Datenstruktur haben, z.

interface ICanTurnToStone
{
   void TurnToStone();
}

public class Troll: ICreature, ICanTurnToStone

Frontend könnte dann sein:

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: Verwendung für Abhängigkeitsinjektion

Die meisten Abhängigkeitsinjektions-Frameworks lassen sich leichter bearbeiten, wenn zwischen dem Front-End-Code und der Back-End-Implementierung eine sehr lose Kopplung besteht. Wenn wir unser obenstehendes Fabrikbeispiel verwenden und von uns eine Schnittstelle implementieren lassen:

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

Unser Frontend könnte dann über den Konstruktor (normalerweise) über diesen eingeblendet werden (z. B. einen MVC-API-Controller):

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);
   }
}

Mit unserem DI-Framework (z. B. Ninject oder Autofac) können wir sie so einrichten, dass zur Laufzeit immer dann eine Instanz von CreatureFactory erstellt wird, wenn eine ICreatureFactory in einem Konstruktor benötigt wird. Dies macht unseren Code schön und einfach. 

Das bedeutet auch, dass wir, wenn wir einen Unit-Test für unseren Controller schreiben, eine verspottete ICreatureFactory bereitstellen können (z. B. wenn die konkrete Implementierung einen DB-Zugriff erfordert, wir nicht wollen, dass unsere Unit-Tests davon abhängig sind) und den Code in unserem Controller problemlos testen können .

D: Es gibt andere Verwendungen, z. Sie haben zwei Projekte A und B, die aus "alten" Gründen nicht gut strukturiert sind, und A hat einen Bezug zu B. 

In B finden Sie dann eine Funktionalität, die bereits eine Methode in A aufrufen muss. Sie können dies nicht mit konkreten Implementierungen tun, da Sie einen Zirkelverweis erhalten.

Sie können eine Schnittstelle in B deklarieren, die dann von der Klasse in A implementiert wird. Ihrer Methode in B kann eine Instanz einer Klasse übergeben werden, die die Schnittstelle problemlos implementiert, obwohl das konkrete Objekt einen Typ in A hat.

99
Paddy

Hier sind Ihre Beispiele wieder erläutert:

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
    }
}
31
Shamim Hafiz

Die obigen Beispiele sind nicht sinnvoll. Sie können alle obigen Beispiele mithilfe von Klassen ausführen (abstrakte Klasse, wenn Sie möchten, dass sich diese nur als contract verhält):

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 */ }
}

Sie erhalten dasselbe Verhalten wie bei der Schnittstelle. Sie können einen List<Food> erstellen und iterieren, dass Sie nicht wissen, welche Klasse oben sitzt.

Ein besseres Beispiel wäre Mehrfachvererbung:

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 */ }
}

Dann können Sie alle als MenuItem verwenden und kümmern sich nicht darum, wie sie mit jedem Methodenaufruf umgehen.

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();
    }
}
20

Einfache Erklärung mit Analogie

Das zu lösende Problem: Was ist der Zweck des Polymorphismus?

Analogie: Ich bin also ein Vorarbeiter auf einer Baustelle.

Handwerker laufen ständig auf der Baustelle. Ich weiß nicht, wer durch diese Türen gehen wird. Aber im Grunde sage ich ihnen, was sie tun sollen.

  1. Wenn es ein Schreiner ist, sage ich: Holzgerüst bauen.
  2. Wenn es ein Klempner ist, sage ich: "Rohrleitungen einrichten"
  3. Wenn es ein Elektriker ist, sage ich: "Ziehen Sie die Kabel heraus und ersetzen Sie sie durch faseroptische Kabel".

Das Problem bei der obigen Vorgehensweise ist, dass ich: (i) wissen muss, wer in diese Tür geht, und je nachdem, wer es ist, muss ich ihnen sagen, was zu tun ist. Das heißt, ich muss alles über ein bestimmtes Geschäft wissen. Mit diesem Ansatz sind Kosten/Nutzen verbunden:

Die Auswirkungen zu wissen, was zu tun ist:

  • Das heißt, wenn sich der Code des Zimmermanns von: BuildScaffolding() in BuildScaffold() ändert (dh eine geringfügige Namensänderung), muss ich auch die aufrufende Klasse (dh die Foreperson-Klasse) ändern - Sie müssen zwei wird anstelle von (im Wesentlichen) nur einem Code geändert. Bei Polymorphismus müssen Sie (im Wesentlichen) nur eine Änderung vornehmen, um dasselbe Ergebnis zu erzielen.

  • Zweitens müssen Sie nicht ständig fragen: wer sind Sie? ok mach das ... wer bist du? ok das ..... Polymorphismus - es DRYT den Code und ist in bestimmten Situationen sehr effektiv:

  • mit Polymorphismus können Sie problemlos weitere Klassen von Handwerkern hinzufügen, ohne den vorhandenen Code zu ändern. (d. h. das zweite der SOLID Designprinzipien: Open-Close-Prinzip). 

Die Lösung

Stellen Sie sich ein Szenario vor, in dem ich, egal wer durch die Tür geht, sagen kann: "Work ()", und sie erledigen ihren Respekt, auf den sie sich spezialisiert haben: Der Klempner würde sich mit Rohren beschäftigen und der Elektriker mit Kabeln.

Der Vorteil dieses Ansatzes ist folgender: (i) Ich muss nicht genau wissen, wer durch diese Tür hereinkommt. Ich muss nur wissen, dass es eine Art von Tradie ist und dass sie arbeiten können und zweitens (ii) Ich muss nichts über diesen speziellen Handel wissen. Der Tradie wird sich darum kümmern.

Also stattdessen:

If(electrician) then  electrician.FixCablesAndElectricity() 

if(plumber) then plumber.IncreaseWaterPressureAndFixLeaks() 

Ich kann so etwas machen:

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()!!

Was ist der vorteil

Der Vorteil besteht darin, dass der Foreperson seinen Code nicht ändern muss, wenn sich die spezifischen Arbeitsanforderungen des Schreiners usw. ändern - er muss es nicht wissen oder sich darum kümmern. Alles was zählt ist, dass der Schreiner weiß, was mit Work () gemeint ist. Zweitens, wenn eine neue Art von Bauarbeiter auf die Baustelle kommt, muss der Vorarbeiter nichts über den Handel wissen - alles, was den Vorarbeiter betrifft, ist, ob der Bauarbeiter (zB Welder, Glazier, Tiler usw.) dies kann erledige deine Arbeit ().


Illustriertes Problem und Lösung (mit und ohne Schnittstellen):

Keine Schnittstelle (Beispiel 1):

 Example 1: without an interface

Keine Schnittstelle (Beispiel 2):

 Example 2: without an interface

Mit einer Schnittstelle:

 Example 3: The benefits of Using an Interface

Zusammenfassung

Über eine Benutzeroberfläche können Sie die Person dazu bringen, die ihnen zugewiesene Arbeit zu erledigen, ohne dass Sie genau wissen, wer sie ist oder was genau sie tun können. Auf diese Weise können Sie problemlos neue Arten von Handelstypen hinzufügen, ohne den vorhandenen Code zu ändern (technisch gesehen ändern Sie ihn nur geringfügig). Dies ist der eigentliche Vorteil eines OOP Ansatzes gegenüber einem funktionalen Programmiermethodik.

Wenn Sie eines der oben genannten Dinge nicht verstehen oder wenn es nicht eindeutig ist, fragen Sie in einem Kommentar, und ich werde versuchen, die Antwort zu verbessern.

18
BKSpurgeon

In Abwesenheit von duck , da Sie es in Python verwenden können, verlässt sich C # auf Schnittstellen, um Abstraktionen bereitzustellen. Wenn es sich bei den Abhängigkeiten einer Klasse nur um konkrete Typen handelt, können Sie keinen anderen Typ übergeben - mithilfe von Schnittstellen können Sie jeden Typ übergeben, der die Schnittstelle implementiert.

11
BrokenGlass

Das Pizza-Beispiel ist schlecht, weil Sie eine abstrakte Klasse verwenden sollten, die die Bestellung abwickelt, und die Pizzas beispielsweise den Pizzatyp überschreiben sollten.

Sie verwenden Schnittstellen, wenn Sie über eine gemeinsam genutzte Eigenschaft verfügen, Ihre Klassen jedoch von verschiedenen Orten erben oder wenn Sie keinen allgemeinen Code haben, den Sie verwenden könnten. Zum Beispiel handelt es sich hierbei um Dinge, die IDisposable entsorgt werden können. Sie wissen, dass sie entsorgt werden. Sie wissen einfach nicht, was passiert, wenn sie entsorgt werden.

Eine Schnittstelle ist nur ein Vertrag, der Ihnen sagt, was ein Objekt tun kann, welche Parameter und welche Rückgabetypen zu erwarten sind.

11
bevacqua

Betrachten Sie den Fall, in dem Sie die Basisklassen nicht kontrollieren oder besitzen.

Nehmen Sie beispielsweise visuelle Steuerelemente an. In .NET für Winforms erben sie alle von der Basisklasse Control, die vollständig in .NET Framework definiert ist.

Nehmen wir an, Sie erstellen benutzerdefinierte Steuerelemente. Sie möchten neue Schaltflächen, Textfelder, Listenansichten, Raster usw. erstellen, und Sie möchten, dass alle Funktionen über bestimmte Funktionen verfügen, die für Ihre Steuerelemente einzigartig sind.

Zum Beispiel möchten Sie vielleicht eine gemeinsame Methode zum Umgang mit dem Thema oder eine gemeinsame Methode zur Lokalisierung.

In diesem Fall können Sie nicht "nur eine Basisklasse erstellen", da Sie alles, das sich auf Steuerelemente bezieht, neu implementieren müssen.

Stattdessen werden Sie von Button, TextBox, ListView, GridView usw. abstammen und Ihren Code hinzufügen.

Dies stellt jedoch ein Problem dar. Wie können Sie jetzt ermitteln, welche Steuerelemente "Ihre" sind? Wie können Sie Code erstellen, der besagt, dass "für alle meine Steuerelemente in dem Formular das Design auf X gesetzt ist.

Schnittstellen eingeben.

Schnittstellen sind eine Möglichkeit, ein Objekt zu betrachten, um festzustellen, dass das Objekt einen bestimmten Vertrag einhält.

Sie würden "YourButton" erstellen, von Button abstammen und Unterstützung für alle Schnittstellen hinzufügen, die Sie benötigen.

So könnten Sie Code wie folgt schreiben:

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

Ohne Schnittstellen wäre dies nicht möglich, stattdessen müssten Sie Code wie folgt schreiben:

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 ....
}

In diesem Fall können (und würden Sie wahrscheinlich) einfach eine Pizza-Basisklasse definieren und von ihnen erben. Es gibt jedoch zwei Gründe, aus denen Sie mit Interfaces Dinge tun können, die auf andere Weise nicht erreicht werden können:

  1. Eine Klasse kann mehrere Schnittstellen implementieren. Es definiert nur Funktionen, die die Klasse haben muss. Die Implementierung einer Reihe von Schnittstellen bedeutet, dass eine Klasse mehrere Funktionen an verschiedenen Orten erfüllen kann.

  2. Eine Schnittstelle kann in einem größeren Bereich als die Klasse oder der Aufrufer definiert werden. Dies bedeutet, dass Sie die Funktionalität voneinander trennen können, die Projektabhängigkeit voneinander trennen und die Funktionalität in einem Projekt oder einer Klasse sowie die Implementierung an anderer Stelle beibehalten können.

Eine Konsequenz von 2 ist, dass Sie die verwendete Klasse ändern können, indem Sie lediglich die entsprechende Schnittstelle implementieren.

7

Beachten Sie, dass Sie in C # keine Mehrfachvererbung verwenden können, und schauen Sie sich Ihre Frage erneut an. 

6

Schnittstelle = Vertrag, verwendet für lose Kopplung (siehe GREIFEN

5

Eine Schnittstelle ist wirklich ein Vertrag, dem die implementierenden Klassen folgen müssen. Sie ist die Basis für so ziemlich jedes Designmuster, das ich kenne.

In Ihrem Beispiel wird die Schnittstelle erstellt, weil dann garantiert alles implementiert wurde, was IS A Pizza, dh die Pizza-Schnittstelle implementiert 

public void Order();

Nach Ihrem erwähnten Code könnten Sie so etwas haben:

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

Auf diese Weise verwenden Sie Polymorphismus und alles, was Ihnen wichtig ist, ist, dass Ihre Objekte auf order () reagieren.

4
Oscar Gomez

Wenn ich an einer API zum Zeichnen von Formen arbeite, möchte ich vielleicht DirectX- oder Grafikaufrufe oder OpenGL verwenden. Also werde ich eine Schnittstelle erstellen, die meine Implementierung von dem, was Sie nennen, abstrahiert.

Sie rufen also eine Factory-Methode auf: MyInterface i = MyGraphics.getInstance(). Dann haben Sie einen Vertrag, damit Sie wissen, welche Funktionen Sie in MyInterface erwarten können. Sie können also i.drawRectangle oder i.drawCube aufrufen und wissen, dass die Funktionen unterstützt werden, wenn Sie eine Bibliothek durch eine andere austauschen.

Dies ist umso wichtiger, wenn Sie Dependency Injection verwenden, da Sie Implementierungen in einer XML-Datei auslagern können.

Möglicherweise verfügen Sie über eine Kryptobibliothek, die exportiert werden kann, die für den allgemeinen Gebrauch bestimmt ist, und eine andere, die nur für amerikanische Unternehmen erhältlich ist. Der Unterschied besteht darin, dass Sie eine Konfigurationsdatei ändern und der Rest des Programms nicht geändert.

Dies wird bei Sammlungen in .NET häufig verwendet, da Sie beispielsweise nur List-Variablen verwenden sollten und sich keine Sorgen machen müssen, ob es sich um eine ArrayList oder LinkedList handelt.

Solange Sie in die Schnittstelle codieren, kann der Entwickler die tatsächliche Implementierung ändern und der Rest des Programms bleibt unverändert.

Dies ist auch beim Komponententest hilfreich, da Sie ganze Schnittstellen ausspionieren können, sodass ich nicht zu einer Datenbank gehen muss, sondern zu einer verspotteten Implementierung, die nur statische Daten zurückgibt. So kann ich meine Methode ohne Bedenken testen Die Datenbank ist zur Wartung aus oder nicht.

4
James Black

Ich habe auf dieser Seite nach dem Wort "Komposition" gesucht und es nicht ein einziges Mal gesehen. Diese Antwort ist sehr ergänzend zu den oben genannten Antworten.

Einer der absolut entscheidenden Gründe für die Verwendung von Schnittstellen in einem objektorientierten Projekt besteht darin, dass Sie Kompositionen gegenüber Vererbung bevorzugen. Durch die Implementierung von Schnittstellen können Sie Ihre Implementierungen von den verschiedenen Algorithmen trennen, die Sie auf sie anwenden.

Dieses großartige "Decorator Pattern" -Tutorial von Derek Banas (das komischerweise auch Pizza als Beispiel verwendet) ist eine lohnende Illustration:

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

3
Jay Edwards

Ich bin überrascht, dass nicht viele Posts den wichtigsten Grund für eine Schnittstelle enthalten: Design Patterns . Es ist das größere Bild der Verwendung von Verträgen, und obwohl es eine Syntaxverzierung ist, um Code zu bearbeiten (um ehrlich zu sein, ignoriert der Compiler sie wahrscheinlich nur), sind Abstraktion und Schnittstellen für OOP, menschliches Verständnis und komplexe Systemarchitekturen von zentraler Bedeutung.

Lassen Sie uns die Pizza-Analogie erweitern, um eine 3-Gänge-Vollkornmahlzeit zu sagen. Wir haben immer noch die Kernschnittstelle Prepare() für alle unsere Lebensmittelkategorien, aber wir haben auch abstrakte Deklarationen für die Kursauswahl (Vorspeise, Hauptgericht, Nachspeise) und unterschiedliche Eigenschaften für Lebensmittelarten (pikant/süß, vegetarisch/nicht vegetarisch) , glutenfrei etc).

Basierend auf diesen Spezifikationen könnten wir das Abstract Factory -Muster implementieren, um den gesamten Prozess zu konzeptualisieren, aber Schnittstellen verwenden, um sicherzustellen, dass nur die Grundlagen konkret sind. Alles andere könnte flexibel werden oder zum Polymorphismus ermutigen, jedoch die Kapselung zwischen den verschiedenen Klassen von Course beibehalten, die die ICourse-Schnittstelle implementieren. 

Wenn ich mehr Zeit hätte, würde ich gerne ein vollständiges Beispiel dafür erstellen, oder jemand kann dies für mich erweitern, aber zusammenfassend wäre eine C # -Schnittstelle das beste Werkzeug bei der Gestaltung dieser Art von System.

2
ShaunnyBwoy

Hier ist eine Schnittstelle für Objekte mit rechteckiger Form:

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

Es ist lediglich erforderlich, dass Sie Möglichkeiten implementieren, um auf die Breite und Höhe des Objekts zuzugreifen.

Nun definieren wir eine Methode, die für jedes Objekt geeignet ist, das IRectangular ist:

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

Dadurch wird die Fläche eines rechteckigen Objekts zurückgegeben.

Wir implementieren eine Klasse SwimmingPool, die rechteckig ist:

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; }
}

Und noch eine Klasse House, die auch rechteckig ist:

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; }
}

Aus diesem Grund können Sie die Area-Methode für Häuser oder Schwimmbäder aufrufen:

var house = new House(2, 3);

var pool = new SwimmingPool(3, 4);

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

Auf diese Weise können Ihre Klassen das Verhalten (statische Methoden) von einer beliebigen Anzahl von Schnittstellen "erben".

2
dharmatech

Schnittstellen dienen zum Anwenden einer Verbindung zwischen verschiedenen Klassen. Zum Beispiel haben Sie eine Klasse für Auto und einen Baum;

public class Car { ... }

public class Tree { ... }

sie möchten eine brennbare Funktionalität für beide Klassen hinzufügen. Aber jede Klasse hat ihre eigene Art zu brennen. so machst du einfach;

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

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

Eine Schnittstelle definiert einen Vertrag zwischen dem Anbieter einer bestimmten Funktionalität und den entsprechenden Verbrauchern. Sie entkoppelt die Implementierung vom Vertrag (Interface). Sie sollten sich objektorientierte Architektur und Design ansehen. Sie können mit wikipedia beginnen: http://en.wikipedia.org/wiki/Interface_(computing)

2
home

Sie erhalten Schnittstellen, wenn Sie sie brauchen :) Sie können Beispiele studieren, aber Sie brauchen Aha! Effekt, um sie wirklich zu bekommen.

Jetzt wissen Sie, was Schnittstellen sind, einfach ohne sie zu codieren. Früher oder später werden Sie auf ein Problem stoßen, bei dem die Verwendung von Schnittstellen das Natürlichste ist.

2
sventevit

Es gibt viele gute Antworten, aber ich möchte es aus einer etwas anderen Perspektive versuchen.

Sie sind möglicherweise mit den SOLID - Prinzipien des objektorientierten Designs vertraut. In Summe:

S - Prinzip der Einzelverantwortung O - Open/Closed - Prinzip L - Liskov - Substitutionsprinzip I - Schnittstellentrennungsprinzip D - Abhängigkeitsinversion

Das Befolgen der SOLID - Prinzipien hilft dabei, Code zu erzeugen, der sauber, gut faktorisiert, zusammenhängend und lose gekoppelt ist. In Anbetracht dessen:

"Abhängigkeitsmanagement ist die wichtigste Herausforderung in Software auf jeder Skala" (Donald Knuth)

dann ist alles, was beim Abhängigkeitsmanagement hilft, ein großer Gewinn. Schnittstellen und das Prinzip der Inversion der Abhängigkeit helfen wirklich, den Code von den Abhängigkeiten von konkreten Klassen zu entkoppeln, sodass Code eher als Verhalten als als Implementierung beschrieben werden kann. Dies hilft, den Code in Komponenten aufzuteilen, die zur Laufzeit anstelle der Kompilierzeit zusammengestellt werden können, und bedeutet, dass diese Komponenten problemlos ein- und ausgebaut werden können, ohne dass der Rest des Codes geändert werden muss.

Schnittstellen helfen insbesondere beim Prinzip der Abhängigkeitsinversion, bei dem Code zu einer Sammlung von Diensten zusammengefasst werden kann, wobei jeder Dienst von einer Schnittstelle beschrieben wird. Services können dann zur Laufzeit in Klassen "injiziert" werden, indem sie als Konstruktorparameter übergeben werden. Diese Technik wird wirklich kritisch, wenn Sie mit dem Schreiben von Komponententests beginnen und testgetriebene Entwicklung verwenden. Versuch es! Sie werden schnell verstehen, wie Schnittstellen dazu beitragen, den Code in überschaubare Blöcke aufzuteilen, die einzeln getestet werden können.

1
Tim Long

Über Schnittstellen lässt sich am einfachsten nachdenken, was Vererbung bedeutet. Wenn die Klasse CC die Klasse C erbt, bedeutet dies Folgendes:

  1. Die Klasse CC kann alle öffentlichen oder geschützten Mitglieder der Klasse C wie ihre eigenen verwenden und muss daher nur Dinge implementieren, die in der übergeordneten Klasse nicht vorhanden sind.
  2. Ein Verweis auf einen CC kann übergeben oder einer Routine oder Variablen zugewiesen werden, die einen Verweis auf ein C ..__ erwartet.

Diese beiden Funktionen der Vererbung sind in gewissem Sinne unabhängig; Obwohl die Vererbung beide gleichzeitig gilt, ist es auch möglich, die zweite ohne die erste anzuwenden. Dies ist nützlich, da das Erben eines Objekts zum Erben von Mitgliedern von zwei oder mehr nicht verwandten Klassen viel komplizierter ist als das Ermöglichen, dass ein Objekttyp durch mehrere Typen ersetzt werden kann.

Eine Schnittstelle ähnelt gewissermaßen einer abstrakten Basisklasse, jedoch mit einem wichtigen Unterschied: Ein Objekt, das eine Basisklasse erbt, kann keine andere Klasse erben. Im Gegensatz dazu kann ein Objekt eine Schnittstelle implementieren, ohne die Fähigkeit zu beeinträchtigen, eine gewünschte Klasse zu erben oder andere Schnittstellen zu implementieren.

Ein nettes Feature (im .net-Framework, IMHO nicht ausgelastet) ist, dass es möglich ist, deklarativ anzugeben, was ein Objekt tun kann. Einige Objekte möchten beispielsweise ein Datenquellenobjekt, von dem sie die Dinge per Index abrufen können (wie dies bei einer Liste möglich ist), müssen jedoch dort nichts speichern. Andere Routinen benötigen ein Data Depository-Objekt, in dem sie Dinge nicht über einen Index speichern können (wie bei Collection.Add), sie müssen jedoch nichts zurücklesen. Einige Datentypen ermöglichen den Zugriff über den Index, das Schreiben jedoch nicht. andere erlauben das Schreiben, erlauben aber nicht den Zugriff nach Index. Einige erlauben natürlich beides.

Wenn ReadableByIndex und Appendable nicht in Beziehung stehende Basisklassen wären, wäre es nicht möglich, einen Typ zu definieren, der sowohl an Dinge übergeben werden könnte, die einen ReadableByIndex erwarten, als auch an Dinge, die ein Appendable erwarten. Man könnte versuchen, dies abzumildern, indem man ReadableByIndex oder Appendable vom anderen ableitet. Die abgeleitete Klasse müsste öffentliche Mitglieder für beide Zwecke zur Verfügung stellen, warnt jedoch vor einigen öffentlichen Mitgliedern, die möglicherweise nicht funktionieren. Einige Klassen und Schnittstellen von Microsoft machen das, aber das ist ziemlich icky. Ein saubererer Ansatz besteht darin, Schnittstellen für die verschiedenen Zwecke zu haben und dann Objekte implementieren zu lassen, die Schnittstellen für die Dinge implementieren, die sie tatsächlich tun können. Wenn einer über eine Schnittstelle IReadableByIndex und eine andere Schnittstelle IAppendable verfügte, könnten Klassen, die den einen oder den anderen ausführen könnten, die entsprechenden Schnittstellen implementieren.

0
supercat

Für mich ist ein Vorteil einer Schnittstelle, dass sie flexibler ist als eine abstrakte Klasse. Da Sie nur eine abstrakte Klasse erben können, aber mehrere Schnittstellen implementieren können, werden Änderungen an einem System, das eine abstrakte Klasse an vielen Stellen erbt, problematisch. Wenn es an 100 Stellen vererbt wird, erfordert eine Änderung Änderungen an allen 100. Mit der Schnittstelle können Sie die neue Änderung jedoch in eine neue Schnittstelle einfügen und diese Schnittstelle einfach dort verwenden, wo sie benötigt wird (Interface Seq. Von SOLID). Darüber hinaus scheint die Speicherauslastung so zu sein, als wäre es mit der Schnittstelle weniger, da ein Objekt im Schnittstellenbeispiel nur einmal im Speicher verwendet wird, obwohl an vielen Stellen die Schnittstelle implementiert ist.

0
ScottS

Therese sind wirklich tolle Beispiele.

Bei einer switch-Anweisung müssen Sie nicht mehr jedes Mal warten und wechseln, wenn Sie möchten, dass rio eine bestimmte Aufgabe ausführt. 

Wenn Sie in Ihrem Pizza-Beispiel eine Pizza herstellen möchten, ist die Schnittstelle alles, was Sie brauchen. Von dort aus sorgt jede Pizza für ihre eigene Logik. 

Dies hilft, die Kopplung und die Komplexität der Zyklomatik zu reduzieren. Sie müssen immer noch die Logik implementieren, aber es gibt weniger, die Sie im breiteren Bild nachverfolgen müssen. 

Für jede Pizza können Sie dann die für diese Pizza spezifischen Informationen verfolgen. Was andere Pizzen haben, spielt keine Rolle, da nur die anderen Pizzen wissen müssen.

0
tam

Der Hauptzweck der Schnittstellen besteht darin, dass zwischen Ihnen und jeder anderen Klasse, die diese Schnittstelle implementiert, ein Vertrag abgeschlossen wird, der Ihren Code entkoppelt und Erweiterbarkeit ermöglicht. 

0
Samir Adel

Schnittstellen können auch verkettet werden, um eine weitere Schnittstelle zu erstellen. Diese Fähigkeit, mehrere Schnittstellen zu implementieren, bietet dem Entwickler den Vorteil, Funktionen zu seinen Klassen hinzuzufügen, ohne die aktuellen Klassenfunktionen ändern zu müssen (SOLID-Prinzipien). 

O = "Klassen sollten für die Erweiterung geöffnet sein, aber zur Modifikation geschlossen sein"

0
user2655145