920 Stimmen

Was bedeutet es, "auf eine Schnittstelle zu programmieren"?

Ich habe das schon ein paar Mal gehört und weiß nicht, was es bedeutet. Wann und warum sollten Sie dies tun?

Ich weiß, was Schnittstellen bewirken, aber die Tatsache, dass ich mir darüber nicht im Klaren bin, lässt mich glauben, dass ich sie nicht richtig nutze.

Ist es einfach so, wenn Sie das tun würden:

IInterface classRef = new ObjectWhatever()

Sie können jede Klasse verwenden, die IInterface ? Wann sollten Sie das tun? Das Einzige, was mir einfällt, ist, wenn Sie eine Methode haben und sich nicht sicher sind, welches Objekt außer der Implementierung der Methode übergeben werden soll. IInterface . Ich kann mir nicht vorstellen, wie oft Sie das tun müssen.

Und wie kann man eine Methode schreiben, die ein Objekt aufnimmt, das eine Schnittstelle implementiert? Ist das möglich?

4 Stimmen

Wenn Sie sich daran erinnern können und Ihr Programm optimal sein soll, können Sie kurz vor der Kompilierung die Schnittstellendeklaration gegen die tatsächliche Implementierung austauschen. Die Verwendung einer Schnittstelle fügt eine Ebene der Indirektion hinzu, die zu Leistungseinbußen führt. Verteilen Sie Ihren auf Schnittstellen programmierten Code jedoch...

25 Stimmen

@Ande Turner: Das ist ein schlechter Ratschlag. 1). "Ihr Programm muss optimal sein" ist kein guter Grund für das Auslagern von Schnittstellen! Dann sagen Sie: "Verteilen Sie Ihren programmierten Code trotzdem auf Schnittstellen..." Sie raten also, dass Sie bei Anforderung (1) dann suboptimalen Code veröffentlichen?!?

82 Stimmen

Die meisten der Antworten hier sind nicht ganz richtig. Es bedeutet nicht oder impliziert nicht einmal "das Schlüsselwort interface verwenden". Eine Schnittstelle ist eine Spezifikation, wie etwas zu verwenden ist - gleichbedeutend mit dem Vertrag (schlagen Sie es nach). Getrennt davon ist die Implementierung, die besagt, wie der Vertrag erfüllt wird. Programmieren Sie nur gegen die Garantien der Methode/des Typs, so dass, wenn die Methode/der Typ auf eine Weise geändert wird, die immer noch dem Vertrag entspricht, der Code, der sie verwendet, nicht kaputt geht.

1823voto

Peter Meyer Punkte 25181

Es gibt hier einige wunderbare Antworten auf diese Fragen, die in alle möglichen Details über Schnittstellen und lose Kopplung von Code, Umkehrung der Kontrolle und so weiter gehen. Es gibt einige ziemlich hitzige Diskussionen, daher möchte ich die Gelegenheit nutzen, die Dinge ein wenig herunterzubrechen, um zu verstehen, warum eine Schnittstelle nützlich ist.

Als ich zum ersten Mal mit Schnittstellen in Berührung kam, war auch ich verwirrt über ihre Bedeutung. Ich habe nicht verstanden, warum man sie braucht. Wenn wir eine Sprache wie Java oder C# verwenden, haben wir bereits Vererbung, und ich sah Schnittstellen als eine schwächer Form der Vererbung und dachte: "Wozu die Mühe?" In gewisser Weise hatte ich Recht, man kann Schnittstellen als eine Art schwache Form der Vererbung betrachten, aber darüber hinaus verstand ich schließlich ihren Nutzen als Sprachkonstrukt, indem ich sie als Mittel zur Klassifizierung gemeinsamer Merkmale oder Verhaltensweisen betrachtete, die von potenziell vielen nicht verwandten Objektklassen gezeigt wurden.

Ein Beispiel: Sie haben ein SIM-Spiel mit den folgenden Klassen:

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

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

Es ist klar, dass diese beiden Objekte in Bezug auf die direkte Vererbung nichts gemeinsam haben. Aber man könnte sagen, sie sind beide lästig.

Nehmen wir an, unser Spiel benötigt eine Art von Zufallsgenerator. Sache das den Spieler beim Essen stört. Dies könnte ein HouseFly oder eine Telemarketer oder beides - aber wie kann man beides mit einer einzigen Funktion berücksichtigen? Und wie bittet man die verschiedenen Arten von Objekten, ihre "lästigen Aufgaben" auf dieselbe Weise zu erledigen?

Das Wichtigste ist, dass sowohl ein Telemarketer y HouseFly haben ein gemeinsames, frei interpretierbares Verhalten, auch wenn sie sich bei der Modellierung nicht ähneln. Lassen Sie uns also eine Schnittstelle schaffen, die beide implementieren können:

interface IPest {
    void BeAnnoying();
}

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

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

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

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

Wir haben jetzt zwei Klassen, die jede auf ihre eigene Weise nervig sein können. Und sie müssen nicht von der gleichen Basisklasse abgeleitet sein und gemeinsame inhärente Eigenschaften haben - sie müssen lediglich den Vertrag von IPest -- dieser Vertrag ist einfach. Sie müssen nur BeAnnoying . In diesem Zusammenhang können wir das Folgende modellieren:

class DiningRoom {

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

    void ServeDinner() {
        when diningPeople are eating,

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

Hier haben wir einen Speisesaal, der eine Reihe von Gästen und eine Reihe von Schädlingen aufnimmt - beachten Sie die Verwendung der Schnittstelle. Das bedeutet, dass in unserer kleinen Welt, ein Mitglied der Gruppe pests Array könnte eigentlich ein Telemarketer Objekt oder ein HouseFly Objekt.

Le site ServeDinner Methode wird genannt, wenn das Abendessen serviert wird und unsere Leute im Speisesaal essen sollen. In unserem kleinen Spiel verrichten unsere Schädlinge ihre Arbeit - jeder Schädling wird angewiesen, durch den IPest Schnittstelle. Auf diese Weise können wir leicht sowohl Telemarketers y HouseFlys auf ihre eigene Art und Weise ärgerlich sein - uns interessiert nur, dass wir etwas in der DiningRoom Objekt, das ein Schädling ist, interessiert uns nicht wirklich, was es ist, und sie könnten nichts mit anderen gemeinsam haben.

Dieses sehr konstruierte Pseudocode-Beispiel (das sich viel länger hinzog, als ich erwartet hatte) soll einfach nur veranschaulichen, was mir endlich ein Licht aufgegangen ist, wenn es um die Verwendung einer Schnittstelle geht. Ich entschuldige mich im Voraus für die Albernheit des Beispiels, hoffe aber, dass es Ihnen hilft, es zu verstehen. Und natürlich decken die anderen Antworten, die Sie hier erhalten haben, die ganze Bandbreite der Verwendung von Schnittstellen in Entwurfsmustern und Entwicklungsmethoden ab.

4 Stimmen

Eine andere Sache, die man in Betracht ziehen sollte, ist, dass es in manchen Fällen nützlich sein könnte, eine Schnittstelle für Dinge zu haben, die "lästig" sein könnten, und eine Vielzahl von Objekten zu haben, die BeAnnoying als no-op; diese Schnittstelle kann anstelle oder zusätzlich zu der Schnittstelle für Dinge, die lästig sind, existieren (wenn beide Schnittstellen existieren, sind die "Dinge, die sind lästige" Schnittstelle sollte von der "Dinge, die sein könnte lästige" Schnittstelle). Der Nachteil der Verwendung solcher Schnittstellen ist, dass die Implementierungen mit der Implementierung einer "lästigen" Anzahl von Stub-Methoden belastet werden können. Der Vorteil ist, dass...

2 Stimmen

...ist es oft schneller, eine Stub-Methode unnötig aufzurufen, als zu ermitteln, ob die Methode aufgerufen werden sollte. Dies ist ein Teil des Grundes IEnumerator<T> erbt IDisposable . In den allermeisten Fällen wird es für den Verbraucher eines Zählers billiger sein, bedingungslos die Dispose als für den Verbraucher, zu prüfen, ob ein Dispose Anruf notwendig ist. Es ist schade, dass weder .net noch Java es erlauben, Standardimplementierungen für Schnittstellenmethoden anzugeben, da es viele Situationen gibt, in denen Schnittstellen von "optionalen" Methoden profitieren könnten, in denen...

1 Stimmen

...ein klares, vernünftiges "Standard"-Verhalten (manchmal nichts tun, manchmal eine Ausnahme auslösen und oft eine statische Methode oder ein anderes Schnittstellenmitglied auf eine feste Art und Weise aufrufen); eine solche Funktion würde es viel praktischer machen, mehr Typen zu haben, die "Vielleicht"-Schnittstellen implementieren, was die Notwendigkeit der Typüberprüfung und des Typ-Castings zur Laufzeit reduziert.

332voto

Bill the Lizard Punkte 384619

Das konkrete Beispiel, das ich den Schülerinnen und Schülern gebe, ist, dass sie schreiben sollen

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

anstelle von

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

In einem kurzen Programm sehen diese genau gleich aus, aber wenn Sie weitergehen und myList 100 Mal in Ihr Programm einfügen, werden Sie einen Unterschied feststellen können. Die erste Deklaration stellt sicher, dass Sie nur Methoden auf myList die durch die Definition der List Schnittstelle (also keine ArrayList spezifische Methoden). Wenn Sie auf diese Weise für die Schnittstelle programmiert haben, können Sie später entscheiden, dass Sie wirklich brauchen

List myList = new TreeList();

und Sie müssen Ihren Code nur an dieser einen Stelle ändern. Sie wissen bereits, dass der Rest Ihres Codes nichts tut, was durch die Änderung der Umsetzung denn Sie programmierten auf die Schnittstelle .

Die Vorteile sind sogar noch offensichtlicher (denke ich), wenn es um Methodenparameter und Rückgabewerte geht. Nehmen wir zum Beispiel dies:

public ArrayList doSomething(HashMap map);

Diese Methodendeklaration bindet Sie an zwei konkrete Implementierungen ( ArrayList y HashMap ). Sobald diese Methode von einem anderen Code aufgerufen wird, bedeutet jede Änderung dieser Typen wahrscheinlich, dass Sie auch den aufrufenden Code ändern müssen. Es wäre besser, für die Schnittstellen zu programmieren.

public List doSomething(Map map);

Jetzt spielt es keine Rolle mehr, welche Art von List Sie zurückkehren, oder welche Art von Map als Parameter übergeben wird. Änderungen, die Sie innerhalb der doSomething Methode werden Sie nicht gezwungen, den aufrufenden Code zu ändern.

0 Stimmen

Kommentare sind nicht für eine ausführliche Diskussion gedacht; dieses Gespräch wurde in den Chat verschoben .

0 Stimmen

Ich habe eine Frage zu dem Grund, den Sie erwähnt haben "Die erste Deklaration stellt sicher, dass Sie nur Methoden auf myList aufrufen, die durch die Schnittstelle List definiert sind (also keine ArrayList spezifischen Methoden). Wenn Sie auf diese Weise für die Schnittstelle programmiert haben, können Sie später entscheiden, dass Sie wirklich List myList = new TreeList(); benötigen, und Sie müssen Ihren Code nur an dieser einen Stelle ändern." Vielleicht habe ich das falsch verstanden, ich frage mich, warum Sie ArrayList in TreeList ändern müssen, wenn Sie "sicherstellen wollen, dass Sie nur Methoden auf myList aufrufen"?

3 Stimmen

@user3014901 Es gibt eine Reihe von Gründen, warum Sie die Art der Liste, die Sie verwenden, ändern möchten. Eine könnte zum Beispiel eine bessere Leistung beim Nachschlagen haben. Der Punkt ist, dass es einfacher ist, Ihren Code später auf eine andere Implementierung umzustellen, wenn Sie für die List-Schnittstelle programmieren.

93voto

kdgregory Punkte 37614

Programmieren für eine Schnittstelle heißt: "Ich brauche diese Funktionalität und es ist mir egal, woher sie kommt."

Betrachten Sie (in Java) die List Schnittstelle gegenüber der ArrayList y LinkedList konkrete Klassen. Wenn mir nur wichtig ist, dass ich eine Datenstruktur mit mehreren Datenelementen habe, auf die ich durch Iteration zugreifen soll, würde ich eine List (und das ist in 99 % der Fälle der Fall). Wenn ich weiß, dass ich eine konstante Zeitspanne für das Einfügen/Löschen von einem der beiden Enden der Liste benötige, wähle ich vielleicht die LinkedList konkrete Umsetzung (oder, was wahrscheinlicher ist, die Warteschlange Schnittstelle). Wenn ich weiß, dass ich zufälligen Zugriff nach Index benötige, würde ich die ArrayList konkrete Klasse.

1 Stimmen

Ich stimme völlig zu, d.h. die Unabhängigkeit zwischen dem, was getan wird, und dem, wie es getan wird. Wenn man ein System in unabhängige Komponenten aufteilt, erhält man ein System, das einfach und wiederverwendbar ist (siehe Einfach gemacht von dem Mann, der Clojure erfunden hat)

48voto

Jonathan Allen Punkte 65707

Die Programmierung für eine Schnittstelle hat absolut nichts mit abstrakten Schnittstellen zu tun, wie wir sie in Java oder .NET sehen. Es handelt sich nicht einmal um ein OOP-Konzept.

Das bedeutet, dass man sich nicht mit den Interna eines Objekts oder einer Datenstruktur herumschlagen sollte. Verwenden Sie die abstrakte Programmschnittstelle oder API, um mit Ihren Daten zu interagieren. In Java oder C# bedeutet das, dass Sie öffentliche Eigenschaften und Methoden anstelle des einfachen Feldzugriffs verwenden. In C bedeutet das die Verwendung von Funktionen anstelle von reinen Zeigern.

EDIT: Bei Datenbanken bedeutet dies, dass Ansichten und gespeicherte Prozeduren anstelle des direkten Tabellenzugriffs verwendet werden.

5 Stimmen

Beste Antwort. Gamma gibt hier eine ähnliche Erklärung: artima.com/lejava/articles/designprinciples.html (siehe Seite 2). Er bezieht sich auf das OO-Konzept, aber Sie haben Recht: Es ist größer als das.

40voto

tvanfosson Punkte 506878

Die Verwendung von Schnittstellen ist ein Schlüsselfaktor, um Ihren Code leicht testbar zu machen und unnötige Kopplungen zwischen Ihren Klassen zu vermeiden. Indem Sie eine Schnittstelle erstellen, die die Operationen Ihrer Klasse definiert, geben Sie Klassen, die diese Funktionalität nutzen wollen, die Möglichkeit, sie zu verwenden, ohne direkt von Ihrer implementierenden Klasse abhängig zu sein. Wenn Sie später beschließen, eine andere Implementierung zu verwenden, brauchen Sie nur den Teil des Codes zu ändern, in dem die Implementierung instanziiert wird. Der Rest des Codes muss nicht geändert werden, da er von der Schnittstelle und nicht von der implementierenden Klasse abhängt.

Dies ist sehr nützlich bei der Erstellung von Unit-Tests. In der zu testenden Klasse lässt man sie von der Schnittstelle abhängen und injiziert eine Instanz der Schnittstelle in die Klasse (oder eine Fabrik, die es ihr ermöglicht, Instanzen der Schnittstelle nach Bedarf zu erstellen) über den Konstruktor oder einen Eigenschaftssetzer. Die Klasse verwendet die bereitgestellte (oder erstellte) Schnittstelle in ihren Methoden. Wenn Sie Ihre Tests schreiben, können Sie die Schnittstelle nachahmen oder fälschen und eine Schnittstelle bereitstellen, die mit den in Ihrem Einheitstest konfigurierten Daten antwortet. Dies ist möglich, weil die zu testende Klasse nur mit der Schnittstelle und nicht mit ihrer konkreten Implementierung arbeitet. Jede Klasse, die die Schnittstelle implementiert, einschließlich Ihrer Mock- oder Fake-Klasse, ist geeignet.

EDIT: Im Folgenden finden Sie einen Link zu einem Artikel, in dem Erich Gamma sein Zitat "Program to an interface, not an implementation" diskutiert.

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

6 Stimmen

Bitte lesen Sie dieses Interview noch einmal: Gamma sprach natürlich über das OO-Konzept der Schnittstelle, nicht über die JAVA- oder C#-Spezialklasse (ISomething). Das Problem ist, dass die meisten Leute dachten, er würde über das Schlüsselwort sprechen, so dass wir jetzt eine Menge nicht benötigter Schnittstellen (ISomething) haben.

1 Stimmen

Sehr gutes Interview. Bitte seien Sie für zukünftige Leser vorsichtig, das Interview umfasst vier Seiten. Ich würde fast den Browser schließen, bevor ich es sehe.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X