Schnittstellen - Was ist der Sinn?


268

Der Grund für Schnittstellen entgeht mir wirklich. Soweit ich weiß, handelt es sich um eine Art Problemumgehung für die nicht vorhandene Mehrfachvererbung, die in C # nicht vorhanden ist (so wurde mir gesagt).

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

In dem unten angegebenen Beispiel, das einem anderen C # -Schnittstellen-Thread beim Stapelüberlauf entnommen wurde, würde ich anstelle einer Schnittstelle nur eine Basisklasse namens Pizza erstellen.

einfaches Beispiel (entnommen aus einem anderen Stapelü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
    }
}

Ich habe das Gefühl, dass es hier auf SO Duplikate dieser Frage gibt, aber sie scheinen alle nur den Vertragsteil einer Schnittstelle zu erklären, daher bin ich mir nicht sicher, ob sie zutreffen.
Lasse V. Karlsen

7
Ich versuche, ein netter und ordentlicher Benutzer zu sein, und suche meine Antwort zuerst in verschiedenen Foren, bevor ich etwas poste. Leider begannen die meisten von ihnen zu einem späteren Zeitpunkt und der Rest half nicht. Ich hatte bereits Probleme mit dem Grundprinzip "Warum?" wie es mir als unnötig überkompliziert erschien. Übrigens. Vielen Dank an alle für die sehr schnellen Antworten. Ich muss sie alle zuerst verdauen, aber ich denke, ich habe jetzt eine ziemlich gute Vorstellung davon, worum es geht. Es scheint, ich habe es immer aus einem anderen Blickwinkel betrachtet. Vielen dank für Deine Hilfe.
Nebelhom


1
Auch Schnittstellen helfen, Vererbung wie für structTypen herzustellen .
Ja72

3
Hmm, das OP fragte: "Soweit ich weiß, ist die Schnittstelle eine Art Workaround für die nicht existierende Mehrfachvererbung, die in C # nicht existiert. (Abgesehen davon im zitierten Lehrbuch-Pizza-Beispiel) würde ich nur Verwenden Sie eine Basisklasse anstelle einer Schnittstelle. " Und dann gaben die meisten Antworten entweder ein Beispiel, das von einer (abstrakten) Basisklasse implementiert werden kann, oder ein Beispiel, um zu zeigen, wie die Schnittstelle für ein Szenario mit mehreren Vererbungen erforderlich ist. Diese Antworten sind alle gut, aber wiederholen sie nicht nur etwas, was das OP bereits weiß? Kein Wunder, dass OP eine Antwort ohne Beispiele gewählt hat. LOL
RayLuo

Antworten:


175

Der Punkt ist, dass die Schnittstelle einen Vertrag darstellt . Eine Reihe öffentlicher Methoden, die jede implementierende Klasse haben muss. Technisch gesehen regelt die Schnittstelle nur die Syntax, dh welche Methoden es gibt, welche Argumente sie erhalten und was sie zurückgeben. Normalerweise kapseln sie auch die Semantik, allerdings nur durch Dokumentation.

Sie können dann verschiedene Implementierungen einer Schnittstelle haben und diese nach Belieben austauschen. In Ihrem Beispiel können Sie, da jede Pizzainstanz eine IPizzaist, IPizzaüberall dort verwenden, wo Sie eine Instanz eines unbekannten Pizzatyps verarbeiten. Jede Instanz, von der der Typ erbt, IPizzaist garantiert bestellbar, da sie über eine Order()Methode verfügt.

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

Beachten Sie auch, dass eine Schnittstelle im üblichen Sinne nicht unbedingt ein C # sein muss interface, sondern auch eine abstrakte Klasse oder sogar eine normale Klasse sein kann (was nützlich sein kann, wenn alle Unterklassen einen gemeinsamen Code verwenden müssen - in den meisten Fällen interfacereicht jedoch aus).


1
+1, obwohl ich nicht sagen würde, dass die Schnittstelle (im Sinne eines Vertrags) eine abstrakte oder eine normale Klasse sein kann.
Groo

3
Ich würde hinzufügen, dass Sie nicht erwarten können, Schnittstellen in ein paar Minuten zu verstehen. Ich denke, es ist nicht sinnvoll, Schnittstellen zu verstehen, wenn Sie nicht über jahrelange Erfahrung in der objektorientierten Programmierung verfügen. Sie können einige Links zu Büchern hinzufügen. Ich würde vorschlagen: Abhängigkeitsinjektion in .NET, das ist wirklich, wie tief das Kaninchenloch geht, nicht nur eine sanfte Einführung.
Knut

2
Ah, es gibt das Problem, dass ich keine Ahnung von DI habe. Aber ich denke, das Hauptproblem des Fragestellers war, warum sie benötigt werden, wenn in Python alles ohne funktioniert. Das ist es, was der größte Absatz meiner Antwort zu liefern versuchte. Ich denke nicht, dass es notwendig ist, in jedes Muster und jede Übung zu graben, die hier Schnittstellen verwendet.
Joey

1
Nun, dann lautet Ihre Frage: "Warum statisch typisierte Sprachen verwenden, wenn dynamisch typisierte Sprachen einfacher zu programmieren sind?". Obwohl ich nicht der Experte bin, der diese Frage beantwortet, kann ich sagen, dass die Leistung das entscheidende Thema ist. Wenn ein Python-Objekt aufgerufen wird, muss der Prozess zur Laufzeit entscheiden, ob dieses Objekt eine Methode namens "Order" hat. Wenn Sie jedoch ein C # -Objekt aufrufen, wird bereits festgestellt, dass es diese Methode implementiert, und die Eine solche Adresse kann angerufen werden.
Boluc Papuccuoglu

3
@BolucPapuccuoglu: Darüber hinaus kann ein Programmierer, der einen Aufruf von sieht , bei einem statisch typisierten Objekt, wenn man fooImplementierungen kennt IWidget, foo.woozle()in der Dokumentation nachsehen IWidgetund wissen, was diese Methode tun soll . Der Programmierer hat möglicherweise keine Möglichkeit zu wissen, woher der Code für die tatsächliche Implementierung stammt, aber jeder Typ, der den IWidgetSchnittstellenvertrag einhält, wird fooin Übereinstimmung mit diesem Vertrag implementiert . In einer dynamischen Sprache hingegen gäbe es keinen klaren Bezugspunkt für das, was foo.woozle()bedeuten sollte.
Supercat

440

Niemand hat wirklich klar erklärt, wie nützlich Schnittstellen sind, also werde ich es versuchen (und ein wenig eine Idee aus Shamims Antwort stehlen).

Nehmen wir die Idee eines Pizza-Bestellservices. Sie können mehrere Arten von Pizzen haben und eine gemeinsame Aktion für jede Pizza ist die Vorbereitung der Bestellung im System. Jede Pizza muss zubereitet werden, aber jede Pizza wird anders zubereitet . 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 legen, die für Pizza mit tiefem Gericht nicht benötigt werden.

Wenn Sie dies in Code schreiben, können Sie dies technisch einfach 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.
        }
    }
}

Für Deep Dish-Pizzen (in C # -Begriffen) müssen jedoch möglicherweise andere Eigenschaften eingestellt werden Prepare() Methode als bei gefüllter Kruste festgelegt werden. Daher stehen Ihnen viele optionale Eigenschaften zur Verfügung, und die Klasse lässt sich nicht gut skalieren (was passiert, wenn Sie neue hinzufügen) Pizzasorten).

Der richtige Weg, dies zu lösen, ist die Verwendung der Schnittstelle. Die Schnittstelle erklärt, dass alle Pizzas zubereitet werden können, aber jede Pizza kann anders zubereitet werden. Wenn Sie also die folgenden 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
    }
}

Jetzt muss Ihr Bestellcode nicht mehr genau wissen, welche Pizzasorten bestellt wurden, um die Zutaten zu verarbeiten. Es hat nur:

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

Obwohl jede Art von Pizza anders zubereitet wird, muss es diesem Teil des Codes egal sein, um welche Art von Pizza es sich handelt. Er weiß nur, dass es sich um Pizza handelt, und daher wird bei jedem Aufruf Prepareautomatisch jede Pizza korrekt zubereitet basierend auf seinem Typ, auch wenn die Sammlung mehrere Arten von Pizzen enthält.


11
+1, ich mochte das Beispiel der Verwendung einer Liste, um die Verwendung einer Schnittstelle zu zeigen.
Danielunderwood

21
Gute Antwort, aber vielleicht ändern Sie sie, um zu verdeutlichen, warum eine Schnittstelle in diesem Fall besser ist als nur die Verwendung einer abstrakten Klasse (für ein so einfaches Beispiel kann eine abstrakte Klasse tatsächlich besser sein?)
Jez

3
Dies ist die beste Antwort. Es gibt einen kleinen Tippfehler oder Sie kopieren einfach den Code und fügen ihn ein. In der C # -Schnittstelle deklarieren Sie keine Zugriffsmodifikatoren wie z public. Also innerhalb der Schnittstelle sollte es nur seinvoid Prepare();
Dush

26
Ich sehe nicht, wie dies die Frage beantwortet. Dieses Beispiel hätte genauso gut mit einer Basisklasse und einer abstrakten Methode erstellt werden können, worauf genau die ursprüngliche Frage hinwies.
Jamie Kitson

4
Schnittstellen kommen erst dann richtig zur Geltung, wenn viele von ihnen herumlaufen. Sie können nur von einer einzelnen Klasse erben, ob abstrakt oder nicht, aber Sie können so viele verschiedene Schnittstellen in einer einzelnen Klasse implementieren, wie Sie möchten. Plötzlich brauchen Sie keine Tür mehr in einem Türrahmen. Alles, was IOpenable ist, wird funktionieren, sei es eine Tür, ein Fenster, ein Briefkasten, wie Sie es nennen.
Mtnielsen

123

Für mich wurde der Punkt zu Beginn erst klar, als Sie aufhörten, sie als Dinge zu betrachten, die das Schreiben Ihres Codes einfacher / schneller machen - dies ist nicht ihr Zweck. Sie haben eine Reihe von Verwendungszwecken:

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

Angenommen, Sie machen ein einfaches Spiel auf dem Bildschirm und es werden Kreaturen angezeigt, mit denen Sie interagieren.

A: Sie können die Wartung Ihres Codes in Zukunft vereinfachen, indem sie eine lose Kopplung zwischen Ihrem Front-End und Ihrer Back-End-Implementierung einführen.

Sie könnten dies zunächst 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 diese auf Twitter darüber lesen. 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 anfängt, chaotisch zu werden. Sie können hier eine Schnittstelle verwenden, damit Ihr Front-End einmal geschrieben und (hier das wichtige Bit) getestet wird. Anschließend können Sie nach Bedarf weitere Back-End-Elemente einstecken:

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 geht nicht um die interne Implementierung eines Trolls oder eines Orks, sondern nur um die Tatsache, dass sie ICreature implementieren.

Ein wichtiger Punkt, den Sie unter diesem Gesichtspunkt beachten sollten, ist, dass Sie auch leicht eine abstrakte Kreaturenklasse hätten verwenden können, und aus dieser Perspektive hat dies den gleichen Effekt.

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 werden:

CreatureFactory _factory;

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

    creature.Walk();
}

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

B: Angenommen, Sie haben 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 die Abhängigkeitsinjektion

Die meisten Abhängigkeitsinjektions-Frameworks sind einfacher zu bearbeiten, wenn eine sehr lockere Kopplung zwischen dem Front-End-Code und der Back-End-Implementierung besteht. Wenn wir unser Fabrikbeispiel oben nehmen und unsere Fabrik eine Schnittstelle implementieren lassen:

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

Unser Front-End könnte dies dann (z. B. einen MVC-API-Controller) über den Konstruktor injizieren lassen (normalerweise):

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 eine Instanz von CreatureFactory erstellt wird, wenn eine ICreatureFactory in einem Konstruktor benötigt wird - dies macht unseren Code schön und einfach.

Dies bedeutet auch, dass wir beim Schreiben eines Komponententests für unseren Controller eine verspottete ICreatureFactory bereitstellen können (z. B. wenn die konkrete Implementierung einen DB-Zugriff erfordert, möchten wir nicht, dass unsere Komponententests davon abhängen) und den Code in unserem Controller einfach testen können .

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

In B finden Sie dann Funktionen, die eine Methode bereits in A aufrufen müssen. Sie können dies nicht mit konkreten Implementierungen tun, da Sie einen Zirkelverweis erhalten.

Sie können eine Schnittstelle in B deklarieren lassen, die die Klasse in A dann implementiert. Ihrer Methode in B kann eine Instanz einer Klasse übergeben werden, die die Schnittstelle problemlos implementiert, obwohl das konkrete Objekt vom Typ in A ist.


6
Ist es nicht ärgerlich, wenn Sie eine Antwort finden, die versucht hat zu sagen, was Sie waren, aber es ist viel besser - stackoverflow.com/a/93998/117215
Paddy

18
Die gute Nachricht ist jetzt, dass es eine tote Seite ist und deine nicht :). Gutes Beispiel!
Sealer_05

1
Ihre C-Diskussion hat mich ein bisschen verloren. Aber ich mag Ihre A- und B-Diskussionen, weil beide im Wesentlichen erklären, wie Schnittstellen verwendet werden können, um gemeinsame Arten von Funktionen über mehrere Klassen hinweg bereitzustellen. Der Bereich der Schnittstellen, der für mich immer noch unscharf ist, ist die Art und Weise, wie Schnittstellen für eine lockerere Kopplung verwendet werden. Vielleicht war es das, worauf sich Ihre C-Diskussion bezog? Wenn ja, brauche ich wohl ein detaillierteres Beispiel :)
user2075599

1
In Ihrem Beispiel scheint mir ICreature besser geeignet zu sein, eine abstrakte Basisklasse (Kreatur?) Für Troll und Ork zu sein. Dann könnte dort jede gemeinsame Logik zwischen Kreaturen implementiert werden. Das heißt, Sie brauchen die ICreature-Oberfläche überhaupt nicht ...
Skarsnik

1
@Skarsnik - ganz genau darum geht es in dieser Notiz: "Ein wichtiger Punkt, den Sie beachten sollten, wenn Sie dies aus dieser Sicht betrachten, ist, dass Sie auch leicht eine abstrakte Kreaturenklasse hätten verwenden können, und aus dieser Perspektive hat dies dieselbe bewirken."
Paddy

33

Hier sind Ihre Beispiele noch einmal erklärt:

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

Die obigen Beispiele machen wenig Sinn. Sie können alle oben genannten Beispiele mit Klassen ausführen (abstrakte Klasse, wenn Sie möchten, dass sie sich nur als Vertrag 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 das gleiche Verhalten wie bei der Schnittstelle. Sie können ein erstellen List<Food>und wiederholen, ohne zu wissen, welche Klasse oben steht.

Ein adäquateres Beispiel wäre die 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 verwenden MenuItemund sich nicht darum kümmern, 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();
    }
}

2
Ich musste hier ganz nach unten scrollen, um eine Antwort zu finden, die tatsächlich die Frage beantwortet: "Warum nicht einfach eine abstrakte Klasse anstelle einer Schnittstelle verwenden?". Es scheint wirklich so, als würde man nur mit dem Mangel an Mehrfachvererbung von c # umgehen.
SyntaxRules

2
Ja, das ist die beste Erklärung für mich. Die einzige, die klar erklärt, warum die Schnittstelle nützlicher sein kann als eine abstrakte Klasse. (dh: eine Pizza ist ein Menüelement UND ist auch Essen, während es mit einer abstrakten Klasse nur die eine oder andere sein kann, aber nicht beide)
Maxter

25

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 gehen die ganze Zeit auf der Baustelle. Ich weiß nicht, wer durch diese Türen gehen wird. Aber ich sage ihnen im Grunde, was zu tun ist.

  1. Wenn es ein Zimmermann ist, sage ich: Holzgerüste bauen.
  2. Wenn es ein Klempner ist, sage ich: "Rohre aufstellen"
  3. Wenn es ein Elektriker ist, sage ich: "Ziehen Sie die Kabel heraus und ersetzen Sie sie durch Glasfaserkabel."

Das Problem mit dem obigen Ansatz ist, dass ich: (i) wissen muss, wer in diese Tür kommt, und je nachdem, wer es ist, muss ich ihnen sagen, was zu tun ist. Das heißt, ich muss alles über einen bestimmten Handel wissen. Mit diesem Ansatz sind Kosten / Nutzen verbunden:

Die Auswirkungen des Wissens, was zu tun ist:

  • Das heißt, wenn sich der Code des Zimmermanns von: BuildScaffolding()nach ändert BuildScaffold()(dh eine geringfügige Namensänderung), muss ich auch die aufrufende Klasse (dh die ForepersonKlasse) ändern - Sie müssen statt (im Grunde genommen) zwei Änderungen am Code vornehmen ) nur einer. Mit Polymorphismus müssen Sie (im Grunde) nur eine Änderung vornehmen, um das gleiche Ergebnis zu erzielen.

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

  • Mit Polymorphismus können Sie problemlos zusätzliche Klassen von Handwerkern hinzufügen, ohne vorhandenen Code zu ändern. (dh das zweite der SOLID-Konstruktionsprinzipien: Open-Close-Prinzip).

Die Lösung

Stellen Sie sich ein Szenario vor, in dem ich, egal wer die Tür betritt, sagen kann: "Work ()" und sie ihre Respektaufgaben erledigen, auf die sie sich spezialisiert haben: Der Klempner würde sich um Rohre kümmern, und der Elektriker würde sich um Drähte kümmern.

Der Vorteil dieses Ansatzes ist, dass: (i) ich nicht genau wissen muss, wer durch diese Tür hereinkommt - alles, was ich wissen muss, ist, dass sie eine Art Tradie sind und arbeiten können, und zweitens , (ii) Ich muss nichts über diesen bestimmten 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 ist, dass der Vorarbeiter seinen Code nicht ändern muss, wenn sich die spezifischen Arbeitsanforderungen des Tischlers usw. ändern - er muss es nicht wissen oder sich darum kümmern. Alles was zählt ist, dass der Schreiner weiß, was unter Arbeit zu verstehen ist (). Zweitens, wenn ein neuer Typ von Bauarbeiter auf die Baustelle kommt, muss der Vorarbeiter nichts über das Handwerk wissen - der Vorarbeiter kümmert sich nur darum, ob der Bauarbeiter (.eg Schweißer, Glaser, Fliesenleger usw.) dies kann erledige etwas Arbeit ().


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

Keine Schnittstelle (Beispiel 1):

Beispiel 1: ohne Schnittstelle

Keine Schnittstelle (Beispiel 2):

Beispiel 2: ohne Schnittstelle

Mit einer Schnittstelle:

Beispiel 3: Die Vorteile der Verwendung einer Schnittstelle

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 sind oder was sie genau tun kann. Auf diese Weise können Sie problemlos neue Arten (des Handels) hinzufügen, ohne Ihren vorhandenen Code zu ändern (technisch gesehen ändern Sie ihn ein kleines bisschen), und das ist der eigentliche Vorteil eines OOP-Ansatzes gegenüber einer funktionaleren Programmiermethode.

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


4
Plus eins für den Kerl, der in einem Hoodie zur Arbeit kommt.
Yusha

11

In Ermangelung der Ententypisierung, wie Sie sie in Python verwenden können, ist C # auf Schnittstellen angewiesen, um Abstraktionen bereitzustellen. Wenn die Abhängigkeiten einer Klasse alle konkrete Typen wären, könnten Sie keinen anderen Typ übergeben - mithilfe von Schnittstellen können Sie jeden Typ übergeben, der die Schnittstelle implementiert.


3
+1 Richtig, wenn Sie Enten tippen, brauchen Sie keine Schnittstellen. Sie erzwingen jedoch eine stärkere Typensicherheit.
Groo

11

Das Pizza-Beispiel ist schlecht, weil Sie eine abstrakte Klasse verwenden sollten, die die Bestellung abwickelt, und die Pizzen beispielsweise nur 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 gemeinsamen Code haben, den Sie verwenden könnten. Zum Beispiel werden hier Dinge verwendet, die entsorgt werden IDisposablekönnen. Sie wissen, dass sie entsorgt werden. Sie wissen einfach nicht, was passieren wird, wenn sie entsorgt werden.

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


10

Stellen Sie sich den Fall vor, in dem Sie die Basisklassen nicht kontrollieren oder besitzen.

Nehmen wir zum Beispiel visuelle Steuerelemente in .NET für Winforms, die alle von der Basisklasse Control erben, die vollständig im .NET Framework definiert ist.

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

Beispielsweise möchten Sie möglicherweise eine allgemeine Methode zum Behandeln von Themen oder eine allgemeine Methode zum Behandeln der Lokalisierung.

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

Stattdessen steigen Sie von Button, TextBox, ListView, GridView usw. ab und fügen Ihren Code hinzu.

Dies stellt jedoch ein Problem dar. Wie können Sie jetzt identifizieren, welche Steuerelemente "Ihnen" gehören, wie können Sie einen Code erstellen, der besagt, dass "für alle Steuerelemente in dem Formular, das mir gehört, das Thema auf X gesetzt wird".

Schnittstellen eingeben.

Schnittstellen sind eine Möglichkeit, ein Objekt zu betrachten und festzustellen, ob das Objekt einem bestimmten Vertrag entspricht.

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

Auf diese Weise können Sie Code wie folgt schreiben:

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

Dies wäre ohne Schnittstellen 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 ....
}

Ja, ich weiß, du solltest nicht "ist" verwenden und dann wirken, lass das beiseite.
Lasse V. Karlsen

4
seufz ich weiß, aber das ist hier nebensächlich.
Lasse V. Karlsen

7

In diesem Fall könnten (und würden) Sie einfach eine Pizza-Basisklasse definieren und von diesen 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 werden nur Funktionen definiert, die die Klasse haben muss. Durch die Implementierung einer Reihe von Schnittstellen kann eine Klasse mehrere Funktionen an verschiedenen Orten erfüllen.

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

Eine Implikation von 2 ist, dass Sie die verwendete Klasse ändern können, nur dass die entsprechende Schnittstelle implementiert werden muss.


6

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



4

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

Sie rufen also eine Factory-Methode auf : MyInterface i = MyGraphics.getInstance(). Dann haben Sie einen Vertrag, damit Sie wissen, mit welchen Funktionen Sie rechnen können MyInterface. Sie können also aufrufen i.drawRectangleoder i.drawCubewissen, dass die Funktionen unterstützt werden, wenn Sie eine Bibliothek gegen eine andere austauschen.

Dies wird wichtiger, wenn Sie Dependency Injection verwenden, da Sie dann in einer XML-Datei Implementierungen austauschen können.

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

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

Solange Sie auf der 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 verspotten können. Ich muss also nicht zu einer Datenbank gehen, sondern zu einer verspotteten Implementierung, die nur statische Daten zurückgibt, damit ich meine Methode testen kann, ohne mir Sorgen machen zu müssen Die Datenbank ist wegen Wartungsarbeiten nicht verfügbar oder nicht.


4

Eine Schnittstelle ist wirklich ein Vertrag, dem die implementierenden Klassen folgen müssen. Sie ist in der Tat die Basis für so ziemlich jedes mir bekannte Entwurfsmuster.

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

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 Sie interessiert, ist, dass Ihre Objekte auf order () reagieren.


4

Ich habe auf dieser Seite nach dem Wort "Komposition" gesucht und es kein einziges Mal gesehen. Diese Antwort ist sehr viel zusätzlich 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 die Komposition der Vererbung vorziehen können. Durch die Implementierung von Schnittstellen können Sie Ihre Implementierungen von den verschiedenen Algorithmen entkoppeln, die Sie auf sie anwenden.

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

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


2
Ich bin wirklich schockiert, dass dies nicht die beste Antwort ist. Schnittstellen in all ihrer Nützlichkeit sind für die Komposition von größtem Nutzen.
Maciej Sitko

3

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 eigenen Möglichkeiten zu brennen. so machst du einfach;

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

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

2
Die Frage, die mich in solchen Beispielen verfolgt, lautet: Warum hilft das? Kann ich jetzt das Argument vom Typ IBurnable an eine Methode übergeben und alle Klassen mit der IBurnable-Schnittstelle können behandelt werden? Gleich wie das Pizza-Beispiel, das ich gefunden habe. Es ist schön, dass Sie dies tun können, aber ich sehe den Nutzen als solchen nicht. Könnten Sie entweder das Beispiel erweitern (weil ich im Moment sehr dick bin) oder ein Beispiel geben, das damit nicht funktioniert (wieder, weil ich mich im Moment sehr dick fühle). Vielen Dank.
Nebelhom

1
Zustimmen. Interface = "Kann". Klasse / Zusammenfassung Calss = "Is A"
Peter.Wang

3

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

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


3

Ich bin überrascht, dass nicht viele Beiträge den wichtigsten Grund für eine Benutzeroberfläche enthalten: Design Patterns . Es ist das Gesamtbild bei der Verwendung von Verträgen, und obwohl es sich um eine Syntaxdekoration für Maschinencode handelt (um ehrlich zu sein, ignoriert der Compiler sie wahrscheinlich nur), sind Abstraktion und Schnittstellen für OOP, menschliches Verständnis und komplexe Systemarchitekturen von entscheidender Bedeutung.

Erweitern wir die Pizza-Analogie zu einem 3-Gänge-Menü. Wir haben weiterhin die Prepare()Kernschnittstelle für alle unsere Lebensmittelkategorien, aber wir hätten auch abstrakte Erklärungen für die Kursauswahl (Vorspeise, Hauptgericht, Dessert) und unterschiedliche Eigenschaften für Lebensmittelarten (herzhaft / süß, vegetarisch / nicht vegetarisch, glutenfrei etc).

Basierend auf diesen Spezifikationen könnten wir das Abstract Factory- Muster implementieren , um den gesamten Prozess zu konzipieren, aber Schnittstellen verwenden, um sicherzustellen, dass nur die Grundlagen konkret sind. Alles andere könnte flexibel werden oder den Polymorphismus fördern, jedoch die Kapselung zwischen den verschiedenen Klassen der CourseImplementierung der ICourseSchnittstelle beibehalten .

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 beim Entwerfen dieses Systemtyps.


1
Diese Antwort verdient mehr Punkte! Schnittstellen leuchten, wenn sie in entworfenen Mustern verwendet werden, z. B. The State Pattern. Weitere Informationen finden Sie unter plus.google.com/+ZoranHorvat-Programming
Alex Nolasco

3

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

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

Sie müssen lediglich Möglichkeiten implementieren, um auf die Breite und Höhe des Objekts zuzugreifen.

Definieren wir nun eine Methode, die für jedes Objekt funktioniert IRectangular:

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

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

Implementieren wir 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 ebenfalls 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; }
}

Vorausgesetzt, Sie können die anrufen Area Methode für Häuser oder Schwimmbäder :

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


3

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


3

Was?

Schnittstellen sind grundsätzlich ein Vertrag, den alle Klassen implementieren folgen sollten, die Schnittstelle . Sie sehen aus wie eine Klasse, haben aber keine Implementierung.

In C#Schnittstellennamen wird die Konvention durch Präfixieren eines 'I' definiert. Wenn Sie also eine Schnittstelle namens Shapes haben möchten, deklarieren Sie diese alsIShapes

Jetzt, warum ?

Improves code re-usability

Können sagen , Sie zeichnen möchten Circle, Triangle. Sie können sie gruppieren und nennen sie Shapesund haben Methoden zu ziehen Circleund Triangle konkrete Umsetzung aber mit morgen eine schlechte Idee wäre, weil du 2 mehr haben könnte entscheiden Shapes Rectangle& Square. Wenn Sie sie jetzt hinzufügen, besteht eine große Wahrscheinlichkeit, dass Sie andere Teile Ihres Codes beschädigen.

Mit Interface isolieren Sie die unterschiedliche Implementierung vom Vertrag


Live-Szenario Tag 1

Sie wurden gebeten, eine App zum Zeichnen von Kreisen und Dreiecken zu erstellen

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

Live-Szenario Tag 2

Wenn Sie zum Hinzufügen Squareund Hinzufügen aufgefordert Rectanglewurden, müssen Sie lediglich die Implementierung für class Square: IShapesund in Mainder Liste hinzufügen hinzufügenshapes.Add(new Square());


Warum fügen Sie einer 6 Jahre alten Frage eine Antwort hinzu, bei der Dutzende anderer Antworten hunderte Male positiv bewertet wurden? Hier gibt es nichts, was noch nicht gesagt wurde.
Jonathon Reinhart

@ JonathonReinhart, ja, das habe ich mir gedacht, aber dann habe ich mir dieses Beispiel überlegt und die Art und Weise, wie es erklärt wird, hilft, sich auf einen Körper besser zu beziehen als auf die anderen.
Clint

2

Hier gibt es viele gute Antworten, aber ich würde es gerne aus einer etwas anderen Perspektive versuchen.

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

S - Prinzip der Einzelverantwortung O - Prinzip der offenen / geschlossenen Verantwortung L - Prinzip der Liskov-Substitution I - Prinzip der Schnittstellentrennung D - Prinzip der Abhängigkeitsinversion

Das Befolgen der SOLID-Prinzipien hilft dabei, Code zu erstellen, der sauber, gut faktorisiert, zusammenhängend und lose gekoppelt ist. Vorausgesetzt, dass:

"Abhängigkeitsmanagement ist die zentrale Herausforderung bei Software in jeder Größenordnung" (Donald Knuth)

Dann ist alles, was beim Abhängigkeitsmanagement hilft, ein großer Gewinn. Schnittstellen und das Prinzip der Abhängigkeitsinversion helfen wirklich dabei, Code von Abhängigkeiten von konkreten Klassen zu entkoppeln, sodass Code eher in Bezug auf Verhalten als in Bezug auf Implementierungen geschrieben und begründet werden kann . Dies hilft dabei, den Code in Komponenten zu unterteilen, die zur Laufzeit und nicht zur Kompilierungszeit erstellt werden können, und bedeutet auch, dass diese Komponenten ganz einfach ein- und ausgesteckt werden können, ohne dass der Rest des Codes geändert werden muss.

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


1

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


1

Therese sind wirklich gute Beispiele gefragt.

Zum anderen müssen Sie im Fall einer switch-Anweisung nicht mehr jedes Mal warten und wechseln, wenn rio eine Aufgabe auf eine bestimmte Weise ausführen soll.

Wenn Sie in Ihrem Pizza-Beispiel eine Pizza machen möchten, ist die Benutzeroberfläche alles, was Sie brauchen. Von dort aus kümmert sich jede Pizza um ihre eigene Logik.

Dies hilft, die Kopplung und die zyklomatische Komplexität zu reduzieren. Sie müssen die Logik noch implementieren, aber es gibt weniger, die Sie im Gesamtbild im Auge behalten 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, denn nur die anderen Pizzen müssen es wissen.


1

Der einfachste Weg, über Schnittstellen nachzudenken, besteht darin, zu erkennen, 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 verwenden, als wären sie ihre eigenen, und muss daher nur Dinge implementieren, die in der übergeordneten Klasse nicht vorhanden sind.
  2. Eine Referenz auf einen CC kann einer Routine oder Variablen übergeben oder zugewiesen werden, die eine Referenz 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 es viel komplizierter ist, einem Objekt zu erlauben, Mitglieder von zwei oder mehr nicht verwandten Klassen zu erben, als zuzulassen, dass ein Typ mehrere Dinge durch mehrere Typen ersetzt.

Eine Schnittstelle ähnelt einer abstrakten Basisklasse, hat jedoch einen wesentlichen Unterschied: Ein Objekt, das eine Basisklasse erbt, kann keine andere Klasse erben. Im Gegensatz dazu kann ein Objekt eine Schnittstelle implementieren, ohne seine Fähigkeit zu beeinträchtigen, eine gewünschte Klasse zu erben oder andere Schnittstellen zu implementieren.

Ein nettes Merkmal davon (im .net-Framework, IMHO, nicht ausreichend genutzt) ist, dass es möglich ist, deklarativ anzugeben, was ein Objekt tun kann. Einige Objekte möchten beispielsweise ein Datenquellenobjekt, von dem sie Dinge per Index abrufen können (wie dies mit einer Liste möglich ist), müssen dort jedoch nichts speichern. Andere Routinen benötigen ein Daten-Depot-Objekt, in dem sie Dinge nicht nach Index speichern können (wie bei Collection.Add), aber sie müssen 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 per Index. Einige erlauben natürlich beides.

Wenn ReadableByIndex und Appendable nicht verwandte Basisklassen wären, wäre es unmöglich, einen Typ zu definieren, der sowohl an Dinge übergeben werden könnte, die einen ReadableByIndex erwarten, als auch an Dinge, die einen Appendable erwarten. Man könnte versuchen, dies zu mildern, indem ReadableByIndex oder Appendable vom anderen abgeleitet werden. Die abgeleitete Klasse müsste öffentliche Mitglieder für beide Zwecke zur Verfügung stellen, warnt jedoch davor, dass einige öffentliche Mitglieder möglicherweise nicht tatsächlich funktionieren. Einige Klassen und Schnittstellen von Microsoft tun dies, aber das ist ziemlich schwierig. Ein sauberer Ansatz besteht darin, Schnittstellen für die verschiedenen Zwecke zu haben und dann Objekte Schnittstellen für die Dinge implementieren zu lassen, die sie tatsächlich tun können. Wenn eine Schnittstelle IReadableByIndex und eine andere Schnittstelle IAppendable hatte,


1

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

O = "Klassen sollten zur Erweiterung geöffnet, aber zur Änderung geschlossen sein"


1

Für mich ist ein Vorteil / 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 an vielen Stellen eine abstrakte Klasse 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 nur dort verwenden, wo sie benötigt wird (Schnittstellenfolge von SOLID). Darüber hinaus scheint die Speichernutzung bei der Schnittstelle geringer zu sein, da ein Objekt im Schnittstellenbeispiel nur einmal im Speicher verwendet wird, obwohl an vielen Stellen die Schnittstelle implementiert ist.


1

Schnittstellen werden verwendet, um die Konsistenz auf eine Weise zu fördern, die lose gekoppelt ist, wodurch sie sich von abstrakten Klassen unterscheidet, die eng gekoppelt sind. Deshalb wird sie auch allgemein als Vertrag definiert. Welche Klassen, die die Schnittstelle implementieren, halten sich an "Regeln / Syntax" durch die Schnittstelle definiert und es gibt keine konkreten Elemente darin.

Ich werde nur ein Beispiel geben, das von der folgenden Grafik unterstützt wird.

Stellen Sie sich vor, in einer Fabrik gibt es drei Arten von Maschinen. Eine Rechteckmaschine, eine Dreiecksmaschine und eine Polygonmaschine. Die Zeiten sind wettbewerbsfähig und Sie möchten die Bedienerschulung optimieren. Sie möchten sie nur in einer Methode zum Starten und Stoppen von Maschinen trainieren haben eine grüne Starttaste und eine rote Stopptaste. So haben Sie jetzt auf 3 verschiedenen Maschinen eine konsistente Möglichkeit, 3 verschiedene Maschinentypen zu starten und zu stoppen. Stellen Sie sich nun vor, diese Maschinen sind Klassen und die Klassen müssen Start- und Stoppmethoden haben, wie Sie Wird die Konsistenz zwischen diesen Klassen gesteigert, die sehr unterschiedlich sein können? Schnittstelle ist die Antwort.

Geben Sie hier die Bildbeschreibung ein

Als einfaches Beispiel zur Visualisierung könnte man sich fragen, warum man nicht die abstrakte Klasse verwendet. Mit einer Schnittstelle müssen die Objekte nicht direkt miteinander verknüpft oder vererbt werden, und Sie können dennoch die Konsistenz über verschiedene Klassen hinweg verbessern.

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

Lassen Sie mich dies aus einer anderen Perspektive beschreiben. Lassen Sie uns eine Geschichte nach dem Beispiel erstellen, das ich oben gezeigt habe;

Programm, Maschine und IMachine sind die Akteure unserer Geschichte. Das Programm möchte ausgeführt werden, verfügt jedoch nicht über diese Fähigkeit, und die Maschine weiß, wie es ausgeführt wird. Machine und IMachine sind beste Freunde, aber Program spricht nicht mit Machine. Also machen Program und IMachine einen Deal und beschlossen, dass IMachine Program mitteilen wird, wie es ausgeführt werden soll, indem sie Machine (wie ein Reflektor) aussehen.

Und Program lernt mithilfe von IMachine, wie man läuft.

Die Schnittstelle bietet Kommunikation und die Entwicklung lose gekoppelter Projekte.

PS: Ich habe die Methode der konkreten Klasse als privat. Mein Ziel hier ist es, eine lose Kopplung zu erreichen, indem der Zugriff auf konkrete Klasseneigenschaften und -methoden verhindert wird und nur die Möglichkeit besteht, sie über Schnittstellen zu erreichen. (Also habe ich die Methoden der Schnittstellen explizit definiert).


1

Ich weiß, dass ich sehr spät bin .. (fast neun Jahre), aber wenn jemand eine kleine Erklärung will, dann können Sie dies tun:

Mit einfachen Worten, Sie verwenden die Schnittstelle, wenn Sie wissen, was ein Objekt kann oder welche Funktion wir für ein Objekt implementieren werden. Beispiel Einfügen, Aktualisieren und Löschen.

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

Wichtiger Hinweis: Schnittstellen sind IMMER öffentlich.

Hoffe das hilft.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.