Was bedeutet es, auf eine Schnittstelle zu programmieren?


814

Ich habe dies einige Male erwähnt gesehen und bin mir nicht sicher, was es bedeutet. Wann und warum würden Sie das tun?

Ich weiß, was Schnittstellen tun, aber die Tatsache, dass mir dies nicht klar ist, lässt mich denken, dass ich sie nicht richtig benutze.

Ist es nur so, wenn Sie tun würden:

IInterface classRef = new ObjectWhatever()

Sie könnten jede Klasse verwenden, die implementiert IInterface? Wann müssten Sie das tun? Das einzige, woran ich denken kann, ist, wenn Sie eine Methode haben und nicht sicher sind, welches Objekt übergeben wird, außer dass es implementiert wird IInterface. Ich kann mir nicht vorstellen, wie oft Sie das tun müssten.

Wie können Sie auch eine Methode schreiben, die ein Objekt aufnimmt, das eine Schnittstelle implementiert? Ist das möglich?


3
Wenn Sie sich erinnern können und Ihr Programm optimal sein muss, möchten Sie möglicherweise kurz vor der Kompilierung die Schnittstellendeklaration gegen die tatsächliche Implementierung austauschen. Durch die Verwendung einer Schnittstelle wird eine Indirektionsebene hinzugefügt, die einen Leistungseinbruch bewirkt. Verteilen Sie Ihren programmierten Code auf Schnittstellen ...
Ande Turner

18
@Ande Turner: Das ist ein schlechter Rat. 1). "Ihr Programm muss optimal sein" ist kein guter Grund, Schnittstellen auszutauschen! Dann sagen Sie "Verteilen Sie Ihren programmierten Code auf Schnittstellen ...", und weisen Sie darauf hin, dass Sie bei gegebener Anforderung (1) dann nicht optimalen Code freigeben?!?
Mitch Wheat

74
Die meisten Antworten hier sind nicht ganz richtig. Dies bedeutet nicht, dass das Schlüsselwort interface verwendet wird. Eine Schnittstelle ist eine Spezifikation für die Verwendung von etwas - synonym mit dem Vertrag (nachschlagen). Davon getrennt ist die Umsetzung, mit der dieser Vertrag erfüllt wird. Programmieren Sie nur gegen die Garantien der Methode / des Typs, sodass der Code nicht beschädigt wird, wenn die Methode / der Typ so geändert wird, dass der Vertrag noch eingehalten wird.
Jyoungdev

2
@ apollodude217 das ist eigentlich die beste Antwort auf der gesamten Seite. Zumindest für die Frage im Titel, da es hier mindestens 3 ganz unterschiedliche Fragen gibt ...
Andrew Spencer

4
Das grundlegende Problem bei Fragen wie diesen besteht darin, dass davon ausgegangen wird, dass "Programmieren auf eine Schnittstelle" "alles in eine abstrakte Schnittstelle einschließen" bedeutet, was albern ist, wenn man bedenkt, dass der Begriff vor dem Konzept abstrakter Schnittstellen im Java-Stil liegt.
Jonathan Allen

Antworten:


1634

Hier gibt es einige wunderbare Antworten auf diese Fragen, die alle möglichen Details zu Schnittstellen und lose Kopplung von Code, Umkehrung der Steuerung usw. enthalten. Es gibt einige ziemlich berauschende Diskussionen, daher möchte ich die Gelegenheit nutzen, um die Dinge ein wenig aufzuschlüsseln, um zu verstehen, warum eine Benutzeroberfläche nützlich ist.

Als ich zum ersten Mal Schnittstellen ausgesetzt wurde, war auch ich über deren Relevanz verwirrt. Ich habe nicht verstanden, warum du sie brauchst. Wenn wir eine Sprache wie Java oder C # verwenden, haben wir bereits Vererbung und ich habe Schnittstellen als eine schwächere Form der Vererbung angesehen und dachte: "Warum sich die Mühe machen?" In gewissem Sinne hatte ich Recht, man kann sich Schnittstellen als eine Art schwache Form der Vererbung vorstellen, aber darüber hinaus verstand ich ihre Verwendung als Sprachkonstrukt schließlich, indem ich sie als Mittel zur Klassifizierung gemeinsamer Merkmale oder Verhaltensweisen betrachtete, die von gezeigt wurden möglicherweise viele nicht verwandte Klassen von Objekten.

Angenommen, Sie haben ein SIM-Spiel und die folgenden Klassen:

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

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

Offensichtlich haben diese beiden Objekte in Bezug auf die direkte Vererbung nichts gemeinsam. Aber man könnte sagen, sie sind beide nervig.

Nehmen wir an, unser Spiel muss zufällig sein Sache haben , die den Spieler beim Abendessen nervt. Dies könnte ein HouseFlyoder ein Telemarketeroder beides sein - aber wie lässt man beides mit einer einzigen Funktion zu? Und wie bittet man jeden Objekttyp, auf die gleiche Weise "seine nervige Sache zu machen"?

Der Schlüssel zu erkennen ist, dass sowohl a Telemarketer als auch HouseFlyein gemeinsames, lose interpretiertes Verhalten teilen, obwohl sie sich in Bezug auf ihre Modellierung nicht ähneln. Erstellen wir also eine Schnittstelle, 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 jeweils auf ihre Weise nerven können. Und sie müssen nicht aus derselben Basisklasse stammen und gemeinsame inhärente Merkmale aufweisen - sie müssen lediglich den Vertrag von erfüllenIPest - dieser Vertrag ist einfach. Du musst nur BeAnnoying. In dieser Hinsicht können wir Folgendes 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, in dem eine Reihe von Gästen und Schädlingen untergebracht sind - beachten Sie die Verwendung der Benutzeroberfläche. Dies bedeutet, dass in unserer kleinen Welt ein Mitglied derpests Arrays tatsächlich ein TelemarketerObjekt oder ein HouseFlyObjekt sein kann.

Die ServeDinnerMethode wird aufgerufen, wenn das Abendessen serviert wird und unsere Leute im Speisesaal essen sollen. In unserem kleinen Spiel erledigen unsere Schädlinge ihre Arbeit - jeder Schädling wird angewiesen, über die IPestBenutzeroberfläche nervig zu sein . Auf diese Weise können wir leicht beides habenTelemarketers und HouseFlysauf jede ihrer eigenen Arten nerven - es ist uns nur DiningRoomwichtig, dass wir etwas in dem Objekt haben, das ein Schädling ist, es ist uns egal, was es ist, und sie könnten nichts darin haben gemeinsam mit anderen.

Dieses sehr ausgeklügelte Pseudocode-Beispiel (das sich viel länger hinzog als ich erwartet hatte) soll lediglich veranschaulichen, was mich letztendlich dazu gebracht hat, wann wir eine Schnittstelle verwenden könnten. Ich entschuldige mich im Voraus für die Albernheit des Beispiels, hoffe aber, dass es Ihnen beim Verständnis hilft. Und natürlich decken die anderen Antworten, die Sie hier erhalten haben, wirklich die Bandbreite der heutigen Verwendung von Schnittstellen in Entwurfsmustern und Entwicklungsmethoden ab.


3
Eine andere zu berücksichtigende Sache ist, dass es in einigen Fällen nützlich sein kann, eine Schnittstelle für Dinge zu haben, die "ärgerlich" sein könnten, und eine Vielzahl von Objekten BeAnnoyingals No-Op zu implementieren ; Diese Schnittstelle kann für Dinge anstelle von oder zusätzlich zu der Schnittstelle vorhanden sind, die ärgerlich sind (wenn beide Schnittstellen vorhanden sind , die „Dinge , die sind ärgerlich“ Schnittstelle sollte von den „Dingen , die erben könnte ärgerlich“ Schnittstelle). Der Nachteil der Verwendung solcher Schnittstellen besteht darin, dass Implementierungen mit der Implementierung einer "ärgerlichen" Anzahl von Stub-Methoden belastet werden können. Der Vorteil ist, dass ...
Supercat

4
Die Methoden sollen keine abstrakten Methoden darstellen - ihre Implementierung ist für die Frage, die sich auf Schnittstellen konzentriert, irrelevant.
Peter Meyer

33
Das Einkapseln von Verhaltensweisen wie IPest wird als Strategiemuster bezeichnet, nur für den Fall, dass jemand daran interessiert ist, mehr Material zu diesem Thema zu erhalten ...
nckbrz

9
Interessanterweise weisen Sie nicht darauf hin, dass IPest[]Sie aufrufen können , weil die Objekte in den IPest-Referenzen sind, BeAnnoying()weil sie diese Methode haben, während Sie andere Methoden ohne Cast nicht aufrufen können. Es wird jedoch für jedes Objekt eine einzelne BeAnnoying()Methode aufgerufen.
D. Ben Knoble

4
Sehr gute Erklärung ... Ich muss es hier nur sagen: Ich habe noch nie davon gehört, dass Schnittstellen eine Art loser Vererbungsmechanismus sind, aber ich weiß, dass Vererbung als schlechter Mechanismus zum Definieren von Schnittstellen verwendet wird (zum Beispiel in regulärem Python you mach es die ganze Zeit).
Carlos H Romano

283

Das spezifische Beispiel, das ich den Schülern gegeben habe, ist, dass sie schreiben sollten

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

anstatt

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

Diese sehen in einem kurzen Programm genauso aus, aber wenn Sie myList100 Mal in Ihrem Programm verwenden, können Sie einen Unterschied feststellen. Die erste Deklaration stellt sicher, dass Sie nur Methoden aufrufen myList, die von der ListSchnittstelle definiert wurden (also keine ArrayListspezifischen Methoden). Wenn Sie auf diese Weise auf die Schnittstelle programmiert haben, können Sie später entscheiden, dass Sie sie wirklich benötigen

List myList = new TreeList();

und Sie müssen nur Ihren Code an dieser einen Stelle ändern. Sie wissen bereits, dass der Rest Ihres Codes nichts tut, was durch Ändern der Implementierung beschädigt wird, weil Sie auf die Schnittstelle programmiert haben .

Die Vorteile sind noch offensichtlicher (glaube ich), wenn Sie über Methodenparameter und Rückgabewerte sprechen. Nehmen Sie zum Beispiel:

public ArrayList doSomething(HashMap map);

Diese Methodendeklaration bindet Sie an zwei konkrete Implementierungen ( ArrayListund HashMap). Sobald diese Methode von einem anderen Code aufgerufen wird, bedeuten Änderungen an diesen Typen wahrscheinlich, dass Sie auch den aufrufenden Code ändern müssen. Es wäre besser, auf die Schnittstellen zu programmieren.

public List doSomething(Map map);

Jetzt spielt es keine Rolle, welche Art von ListSie zurückgeben oder welche Art von MapParameter übergeben wird. Änderungen, die Sie innerhalb der doSomethingMethode vornehmen, zwingen Sie nicht, den aufrufenden Code zu ändern.


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

Sehr klare Erklärung. Sehr hilfreich
Samuel Luswata

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

1
@ user3014901 Es gibt eine Reihe von Gründen, warum Sie den von Ihnen verwendeten Listentyp ändern möchten. Man könnte zum Beispiel eine bessere Suchleistung haben. Der Punkt ist, dass es einfacher ist, Ihren Code später in eine andere Implementierung zu ändern, wenn Sie auf die List-Oberfläche programmieren.
Bill the Lizard

73

Das Programmieren auf eine Schnittstelle sagt: "Ich brauche diese Funktionalität und es ist mir egal, woher sie kommt."

Betrachten Sie (in Java) die ListSchnittstelle gegenüber den ArrayListund LinkedListkonkreten Klassen. Wenn mir nur wichtig ist, dass ich eine Datenstruktur habe, die mehrere Datenelemente enthält, auf die ich per Iteration zugreifen sollte, würde ich eine auswählen List(und das ist in 99% der Fälle). Wenn ich weiß, dass ich an beiden Enden der Liste ein zeitlich konstantes Einfügen / Löschen benötige, kann ich die LinkedListkonkrete Implementierung auswählen (oder eher die Warteschlangenschnittstelle verwenden). Wenn ich weiß, dass ich einen zufälligen Zugriff per Index benötige, würde ich die ArrayListkonkrete Klasse auswählen .


1
stimme vollkommen zu, dh der Unabhängigkeit zwischen dem, was getan wird, und dem, wie es getan wird. Wenn Sie ein System entlang unabhängiger Komponenten partitionieren, erhalten Sie ein System, das einfach und wiederverwendbar ist (siehe Simple Made Easy von dem Typ, der Clojure erstellt hat)
Beluchin

38

Die Verwendung von Schnittstellen ist ein Schlüsselfaktor, um Ihren Code leicht testbar zu machen und unnötige Kopplungen zwischen Ihren Klassen zu beseitigen. Indem Sie eine Schnittstelle erstellen, die die Operationen für Ihre Klasse definiert, können Klassen, die diese Funktionalität verwenden möchten, diese verwenden, ohne direkt von Ihrer implementierenden Klasse abhängig zu sein. Wenn Sie sich später entscheiden, eine andere Implementierung zu ändern und zu verwenden, müssen Sie nur den Teil des Codes ä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 beim Erstellen von Komponententests. In der zu testenden Klasse hängt dies von der Schnittstelle ab und fügt eine Instanz der Schnittstelle über den Konstruktor oder einen Eigenschaftssetzer in die Klasse ein (oder eine Factory, mit der Instanzen der Schnittstelle nach Bedarf erstellt werden können). Die Klasse verwendet die bereitgestellte (oder erstellte) Schnittstelle in ihren Methoden. Wenn Sie Ihre Tests schreiben, können Sie die Schnittstelle verspotten oder fälschen und eine Schnittstelle bereitstellen, die mit Daten reagiert, die in Ihrem Komponententest konfiguriert wurden. Sie können dies tun, weil sich Ihre zu testende Klasse nur mit der Schnittstelle befasst, nicht mit Ihrer konkreten Implementierung. Jede Klasse, die die Schnittstelle implementiert, einschließlich Ihrer Schein- oder Fake-Klasse, reicht aus.

BEARBEITEN: Unten finden Sie einen Link zu einem Artikel, in dem Erich Gamma sein Zitat "Programmieren auf eine Schnittstelle, keine Implementierung" erläutert.

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


3
Bitte lesen Sie dieses Interview noch einmal: Gamma sprach natürlich über das OO-Konzept der Schnittstelle, nicht über die JAVA- oder die C # -Spezialklasse (ISomething). Das Problem ist, dass die meisten Leute, obwohl er über das Schlüsselwort sprach, so dass wir jetzt viele nicht benötigte Schnittstellen (ISomething) haben.
Sylvain Rodrigue

Sehr gutes Interview. Bitte seien Sie vorsichtig für zukünftige Leser, das Interview enthält vier Seiten. Ich würde den Browser fast schließen, bevor ich ihn sehe.
Ad Infinitum

38

Das Programmieren auf eine Schnittstelle hat absolut nichts mit abstrakten Schnittstellen zu tun, wie wir sie in Java oder .NET sehen. Es ist nicht einmal ein OOP-Konzept.

Was es bedeutet, ist, nicht mit den Interna eines Objekts oder einer Datenstruktur herumzuspielen. Verwenden Sie die Abstract Program Interface oder API, um mit Ihren Daten zu interagieren. In Java oder C # bedeutet dies, dass öffentliche Eigenschaften und Methoden anstelle des unformatierten Feldzugriffs verwendet werden. Für C bedeutet dies, dass Funktionen anstelle von Rohzeigern verwendet werden.

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


5
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.
Sylvain Rodrigue

36

Sie sollten sich mit Inversion of Control befassen:

In einem solchen Szenario würden Sie Folgendes nicht schreiben:

IInterface classRef = new ObjectWhatever();

Sie würden so etwas schreiben:

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

Dies würde in ein regelbasiertes Setup im containerObjekt gehen und das eigentliche Objekt für Sie erstellen, das ObjectWhatever sein könnte. Wichtig ist, dass Sie diese Regel durch etwas ersetzen können, das insgesamt einen anderen Objekttyp verwendet, und Ihr Code weiterhin funktioniert.

Wenn wir IoC von der Tabelle lassen, können Sie Code schreiben, der weiß, dass er mit einem Objekt kommunizieren kann, das etwas Bestimmtes tut , aber nicht, welcher Objekttyp oder wie es es tut.

Dies wäre praktisch, wenn Parameter übergeben werden.

Bei Ihrer Frage in Klammern "Wie können Sie eine Methode schreiben, die ein Objekt aufnimmt, das eine Schnittstelle implementiert? Ist das möglich?", Verwenden Sie in C # einfach den Schnittstellentyp für den Parametertyp wie folgt:

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

Dies wird direkt in das "Gespräch mit einem Objekt, das etwas Bestimmtes tut" eingebunden. Die oben definierte Methode weiß, was von dem Objekt zu erwarten ist, dass es alles in IInterface implementiert, aber es ist egal, um welchen Objekttyp es sich handelt, nur dass es sich an den Vertrag hält, was eine Schnittstelle ist.

Zum Beispiel sind Sie wahrscheinlich mit Taschenrechnern vertraut und haben in Ihren Tagen wahrscheinlich einige verwendet, aber die meiste Zeit sind sie alle unterschiedlich. Auf der anderen Seite wissen Sie, wie ein Standardrechner funktionieren sollte, sodass Sie alle verwenden können, auch wenn Sie nicht die spezifischen Funktionen verwenden können, die jeder Rechner hat, die keiner der anderen hat.

Das ist das Schöne an Schnittstellen. Sie können einen Code schreiben, der weiß, dass Objekte an ihn übergeben werden, von denen er ein bestimmtes Verhalten erwarten kann. Es ist egal, um welche Art von Objekt es sich handelt, nur dass es das erforderliche Verhalten unterstützt.

Lassen Sie mich Ihnen ein konkretes Beispiel geben.

Wir haben ein maßgeschneidertes Übersetzungssystem für Windows Forms. Dieses System durchläuft Steuerelemente in einem Formular und übersetzt jeweils Text. Das System weiß, wie man mit grundlegenden Steuerelementen umgeht, wie dem Steuerelementtyp, der eine Text-Eigenschaft hat, und ähnlichen grundlegenden Dingen, aber für alles Grundlegende ist es nicht ausreichend.

Da Steuerelemente von vordefinierten Klassen erben, über die wir keine Kontrolle haben, können wir eines von drei Dingen tun:

  1. Bauen Sie Unterstützung für unser Übersetzungssystem auf, um genau zu erkennen, mit welcher Art von Steuerung es arbeitet, und um die richtigen Bits zu übersetzen (Wartungsalptraum)
  2. Unterstützung in Basisklassen einbauen (unmöglich, da alle Steuerelemente von verschiedenen vordefinierten Klassen erben)
  3. Schnittstellenunterstützung hinzufügen

Also haben wir nr. 3. Alle unsere Steuerelemente implementieren ILocalizable, eine Schnittstelle, die uns eine Methode bietet, nämlich die Möglichkeit, "sich selbst" in einen Container mit Übersetzungstexten / -regeln zu übersetzen. Daher muss das Formular nicht wissen, welche Art von Steuerelement es gefunden hat, sondern nur, dass es die spezifische Schnittstelle implementiert und dass es eine Methode gibt, mit der es das Steuerelement lokalisieren kann.


31
Warum sollte man IoC gleich zu Beginn erwähnen, da dies nur zu mehr Verwirrung führen würde?
Kevin Le - Khnle

1
Ich stimme zu, ich würde sagen, dass das Programmieren gegen Schnittstellen nur eine Technik ist, um IoC einfacher und zuverlässiger zu machen.
Terjetyl

28

Code für die Schnittstelle Weder die Implementierung hat NICHTS mit Java noch mit ihrem Schnittstellenkonstrukt zu tun.

Dieses Konzept wurde in den Büchern Patterns / Gang of Four bekannt gemacht, gab es aber höchstwahrscheinlich schon lange vorher. Das Konzept existierte sicherlich lange bevor es Java gab.

Das Java Interface-Konstrukt wurde erstellt, um diese Idee (unter anderem) zu unterstützen, und die Leute haben sich zu sehr auf das Konstrukt als Zentrum der Bedeutung und nicht auf die ursprüngliche Absicht konzentriert. Dies ist jedoch der Grund, warum wir öffentliche und private Methoden und Attribute in Java, C ++, C # usw. haben.

Es bedeutet, nur mit der öffentlichen Schnittstelle eines Objekts oder Systems zu interagieren. Machen Sie sich keine Sorgen oder nehmen Sie nicht einmal vorweg, wie es das tut, was es intern tut. Mach dir keine Sorgen darüber, wie es implementiert wird. Im objektorientierten Code haben wir deshalb öffentliche und private Methoden / Attribute. Wir beabsichtigen, die öffentlichen Methoden zu verwenden, da die privaten Methoden nur zur internen Verwendung innerhalb der Klasse zur Verfügung stehen. Sie bilden die Implementierung der Klasse und können nach Bedarf geändert werden, ohne die öffentliche Schnittstelle zu ändern. Angenommen, eine Methode für eine Klasse führt in Bezug auf die Funktionalität jedes Mal dieselbe Operation mit demselben erwarteten Ergebnis aus, wenn Sie sie mit denselben Parametern aufrufen. Es ermöglicht dem Autor, die Funktionsweise der Klasse und ihre Implementierung zu ändern, ohne die Interaktion der Benutzer zu beeinträchtigen.

Und Sie können auf die Schnittstelle programmieren, nicht auf die Implementierung, ohne jemals ein Schnittstellenkonstrukt zu verwenden. Sie können auf die Schnittstelle nicht die Implementierung in C ++ programmieren, die kein Schnittstellenkonstrukt hat. Sie können zwei massive Unternehmenssysteme viel robuster integrieren, solange sie über öffentliche Schnittstellen (Verträge) interagieren, anstatt Methoden für systeminterne Objekte aufzurufen. Es wird erwartet, dass die Schnittstellen bei gleichen Eingabeparametern immer auf die gleiche erwartete Weise reagieren. wenn auf der Schnittstelle implementiert und nicht die Implementierung. Das Konzept funktioniert an vielen Stellen.

Schütteln Sie den Gedanken, dass Java-Schnittstellen irgendetwas mit dem Konzept "Programmieren auf die Schnittstelle, nicht auf die Implementierung" zu tun haben. Sie können helfen, das Konzept anzuwenden, aber sie sind nicht das Konzept.


1
Der erste Satz sagt alles. Dies sollte die akzeptierte Antwort sein.
Madumlao

14

Es hört sich so an, als ob Sie verstehen, wie Schnittstellen funktionieren, sich aber nicht sicher sind, wann Sie sie verwenden sollen und welche Vorteile sie bieten. Hier einige Beispiele, wann eine Schnittstelle sinnvoll wäre:

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

interface ISearchProvider
{
    string Search(string keywords);
}

dann könnte ich GoogleSearchProvider, YahooSearchProvider, LiveSearchProvider usw. erstellen.

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

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

Erstellen Sie dann JpegImageLoader, GifImageLoader, PngImageLoader usw.

Die meisten Add-Ins und Plugin-Systeme arbeiten über Schnittstellen.

Eine weitere beliebte Verwendung ist das Repository-Muster. Angenommen, ich möchte eine Liste mit Postleitzahlen aus verschiedenen Quellen laden

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

Dann könnte ich ein XMLZipCodeRepository, SQLZipCodeRepository, CSVZipCodeRepository usw. erstellen. Für meine Webanwendungen erstelle ich häufig frühzeitig XML-Repositorys, damit ich etwas in Betrieb nehmen kann, bevor die SQL-Datenbank fertig ist. Sobald die Datenbank fertig ist, schreibe ich ein SQLRepository, um die XML-Version zu ersetzen. Der Rest meines Codes bleibt unverändert, da er ausschließlich über Schnittstellen ausgeführt wird.

Methoden können Schnittstellen akzeptieren wie:

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

10

Es macht Ihren Code viel erweiterbarer und einfacher zu pflegen, wenn Sie Sätze ähnlicher Klassen haben. Ich bin ein Junior-Programmierer, also kein Experte, aber ich habe gerade ein Projekt abgeschlossen, für das etwas Ähnliches erforderlich war.

Ich arbeite an clientseitiger Software, die mit einem Server kommuniziert, auf dem ein medizinisches Gerät ausgeführt wird. Wir entwickeln eine neue Version dieses Geräts, die einige neue Komponenten enthält, die der Kunde zeitweise konfigurieren muss. Es gibt zwei Arten neuer Komponenten, die sich unterscheiden, aber auch sehr ähnlich sind. Grundsätzlich musste ich zwei Konfigurationsformulare erstellen, zwei Listenklassen, zwei von allem.

Ich entschied, dass es am besten ist, für jeden Steuerelementtyp eine abstrakte Basisklasse zu erstellen, die fast die gesamte reale Logik enthält, und dann Typen abzuleiten, um die Unterschiede zwischen den beiden Komponenten zu berücksichtigen. Die Basisklassen wären jedoch nicht in der Lage gewesen, Operationen an diesen Komponenten auszuführen, wenn ich mich ständig um Typen kümmern müsste (nun, sie hätten es tun können, aber es hätte in jeder Methode eine "if" -Anweisung oder einen Wechsel gegeben). .

Ich habe eine einfache Schnittstelle für diese Komponenten definiert und alle Basisklassen sprechen mit dieser Schnittstelle. Wenn ich jetzt etwas ändere, funktioniert es so ziemlich überall und ich habe keine Codeduplizierung.


10

Viele Erklärungen da draußen, aber um es noch einfacher zu machen. Nehmen Sie zum Beispiel a List. Man kann eine Liste implementieren mit:

  1. Ein internes Array
  2. Eine verknüpfte Liste
  3. Andere Implementierungen

Wenn Sie eine Schnittstelle erstellen, sagen Sie a List. Sie codieren nur hinsichtlich der Definition der Liste oder was Listin der Realität bedeutet.

Sie können jede Art von Implementierung intern verwenden, beispielsweise eine arrayImplementierung. Angenommen, Sie möchten die Implementierung aus irgendeinem Grund ändern, z. B. aufgrund eines Fehlers oder einer Leistung. Dann müssen Sie nur noch die Deklaration List<String> ls = new ArrayList<String>()in ändern List<String> ls = new LinkedList<String>().

Nirgendwo sonst im Code müssen Sie etwas anderes ändern. Weil alles andere auf der Definition von aufgebaut war List.


8

Wenn Sie in Java programmieren, ist JDBC ein gutes Beispiel. JDBC definiert eine Reihe von Schnittstellen, sagt jedoch nichts über die Implementierung aus. Ihre Anwendungen können gegen diese Schnittstellen geschrieben werden. Theoretisch wählen Sie einen JDBC-Treiber aus und Ihre Anwendung würde einfach funktionieren. Wenn Sie feststellen, dass es einen schnelleren oder "besseren" oder billigeren JDBC-Treiber gibt oder aus welchem ​​Grund auch immer, können Sie Ihre Eigenschaftendatei theoretisch erneut konfigurieren, und ohne Änderungen an Ihrer Anwendung vornehmen zu müssen, funktioniert Ihre Anwendung weiterhin.


Dies ist nicht nur nützlich, wenn ein besserer Treiber verfügbar wird, sondern ermöglicht es auch, den Datenbankanbieter vollständig zu wechseln.
Ken Liu

3
JDBC ist so schlecht, dass es ersetzt werden muss. Finden Sie ein anderes Beispiel.
Joshua

JDBC ist schlecht, aber nicht aus irgendeinem Grund mit Schnittstelle oder Implementierung oder Abstraktionsebenen zu tun. Um das fragliche Konzept zu veranschaulichen, ist es einfach perfekt.
Erwin Smout

8

Das Programmieren auf Schnittstellen ist fantastisch, es fördert die lose Kopplung. Wie @lassevk erwähnte, ist Inversion of Control eine großartige Verwendung davon.

Schauen Sie sich außerdem die SOLID-Prinzipien an . Hier ist eine Videoserie

Es durchläuft ein fest codiertes (stark gekoppeltes Beispiel), betrachtet dann die Schnittstellen und gelangt schließlich zu einem IoC / DI-Tool (NInject).


7

Ich bin spät dran bei dieser Frage, aber ich möchte hier erwähnen, dass die Zeile "Programmieren auf eine Schnittstelle, keine Implementierung" im Buch "GoF (Gang of Four) Design Patterns" eine gute Diskussion hatte.

Es stellte fest, auf p. 18:

Programm auf eine Schnittstelle, keine Implementierung

Deklarieren Sie Variablen nicht als Instanzen bestimmter konkreter Klassen. Legen Sie stattdessen nur eine Schnittstelle fest, die von einer abstrakten Klasse definiert wird. Sie werden feststellen, dass dies ein allgemeines Thema der Entwurfsmuster in diesem Buch ist.

und darüber hinaus begann es mit:

Es gibt zwei Vorteile, Objekte ausschließlich in Bezug auf die durch abstrakte Klassen definierte Schnittstelle zu bearbeiten:

  1. Clients sind sich der spezifischen Objekttypen, die sie verwenden, nicht bewusst, solange die Objekte der von Clients erwarteten Schnittstelle entsprechen.
  2. Clients sind sich der Klassen, die diese Objekte implementieren, nicht bewusst. Clients kennen nur die abstrakten Klassen, die die Schnittstelle definieren.

Mit anderen Worten, schreiben Sie Ihre Klassen nicht so, dass sie eine quack()Methode für Enten und dann eine bark()Methode für Hunde enthalten, da sie für eine bestimmte Implementierung einer Klasse (oder Unterklasse) zu spezifisch sind. Schreiben Sie die Methode stattdessen mit Namen, die allgemein genug sind, um in der Basisklasse verwendet zu werden, z. B. giveSound()oder move(), damit sie für Enten, Hunde oder sogar Autos verwendet werden können, und dann kann der Client Ihrer Klassen nur sagen, .giveSound()anstatt Überlegen Sie, ob Sie den Typ verwenden quack()oder bark()sogar bestimmen möchten, bevor Sie die richtige Nachricht ausgeben, die an das Objekt gesendet werden soll.


6

Zusätzlich zu der bereits ausgewählten Antwort (und den verschiedenen informativen Beiträgen hier) würde ich dringend empfehlen, eine Kopie von Head First Design Patterns zu erwerben . Es ist sehr einfach zu lesen und beantwortet Ihre Frage direkt, erklärt, warum es wichtig ist, und zeigt Ihnen viele Programmiermuster, mit denen Sie dieses Prinzip (und andere) anwenden können.


5

Um die vorhandenen Beiträge zu ergänzen, hilft manchmal das Codieren in Schnittstellen bei großen Projekten, wenn Entwickler gleichzeitig an separaten Komponenten arbeiten. Sie müssen lediglich die Schnittstellen im Voraus definieren und Code in sie schreiben, während andere Entwickler Code in die von Ihnen implementierte Schnittstelle schreiben.


4

Es ist auch gut für Unit-Tests geeignet. Sie können Ihre eigenen Klassen (die den Anforderungen der Schnittstelle entsprechen) in eine davon abhängige Klasse einfügen


4

Es kann vorteilhaft sein, auf Schnittstellen zu programmieren, auch wenn wir nicht auf Abstraktionen angewiesen sind.

Das Programmieren auf Schnittstellen zwingt uns, eine kontextbezogene Teilmenge eines Objekts zu verwenden . Das hilft, weil es:

  1. hindert uns daran, kontextuell unangemessene Dinge zu tun, und
  2. Damit können wir die Implementierung in Zukunft sicher ändern.

Stellen Sie sich beispielsweise eine PersonKlasse vor, die die Friendund die EmployeeSchnittstelle implementiert.

class Person implements AbstractEmployee, AbstractFriend {
}

Im Zusammenhang mit dem Geburtstag der Person programmieren wir auf die FriendBenutzeroberfläche, um zu verhindern, dass die Person wie eine behandelt wird Employee.

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

Im Rahmen der Arbeit der Person programmieren wir auf die EmployeeSchnittstelle, um ein Verwischen der Arbeitsplatzgrenzen zu vermeiden.

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

Großartig. Wir haben uns in verschiedenen Kontexten angemessen verhalten und unsere Software funktioniert gut.

Weit in der Zukunft können wir die Software ziemlich einfach ändern, wenn sich unser Geschäft ändert, um mit Hunden zu arbeiten. Zuerst erstellen wir eine DogKlasse, die sowohl Friendals auch implementiert Employee. Dann wechseln wir sicher new Person()zu new Dog(). Selbst wenn beide Funktionen Tausende von Codezeilen haben, funktioniert diese einfache Bearbeitung, da wir wissen, dass Folgendes zutrifft:

  1. Die Funktion partyverwendet nur die FriendTeilmenge von Person.
  2. Die Funktion workplaceverwendet nur die EmployeeTeilmenge von Person.
  3. Class Dogimplementiert sowohl die Friendals auch die EmployeeSchnittstellen.

Wenn andererseits programmiert wird partyoder dagegen workplaceprogrammiert wird Person, besteht das Risiko, dass beide einen Personspezifischen Code haben. Wenn wir von Personzu wechseln , Dogmüssen wir den Code durchkämmen, um jeden Personspezifischen Code auszulöschen, Dogder nicht unterstützt wird.

Die Moral : Das Programmieren auf Schnittstellen hilft unserem Code, sich angemessen zu verhalten und bereit für Änderungen zu sein. Es bereitet unseren Code auch darauf vor, von Abstraktionen abhängig zu sein, was noch mehr Vorteile bringt.


1
Vorausgesetzt, Sie haben keine übermäßig breiten Schnittstellen.
Casey

4

Wenn ich eine neue Klasse schreibe Swimmer, um die Funktionalität hinzuzufügen, swim()und ein Objekt der Klasse verwenden muss, sagen wir Dog, und diese DogKlasse implementiert eine Schnittstelle, Animaldie deklariert swim().

Am oberen AnimalRand der Hierarchie ( ) ist es sehr abstrakt, während es am unteren Rand ( Dog) sehr konkret ist. Die Art und Weise, wie ich über "Programmieren auf Schnittstellen" nachdenke, ist, dass ich beim Schreiben von SwimmerKlassen meinen Code gegen die Schnittstelle schreiben möchte, die so weit oben in der Hierarchie liegt, die in diesem Fall ein AnimalObjekt ist. Eine Schnittstelle ist frei von Implementierungsdetails und macht Ihren Code daher lose gekoppelt.

Die Implementierungsdetails können mit der Zeit geändert werden. Dies hat jedoch keine Auswirkungen auf den verbleibenden Code, da Sie nur mit der Schnittstelle und nicht mit der Implementierung interagieren. Es ist Ihnen egal, wie die Implementierung aussieht ... Sie wissen nur, dass es eine Klasse geben wird, die die Schnittstelle implementiert.


3

Um dies richtig zu machen, besteht der Vorteil einer Schnittstelle darin, dass ich den Aufruf einer Methode von einer bestimmten Klasse trennen kann. Erstellen Sie stattdessen eine Instanz der Schnittstelle, in der die Implementierung von der von mir ausgewählten Klasse angegeben wird, die diese Schnittstelle implementiert. Auf diese Weise kann ich viele Klassen haben, die ähnliche, aber leicht unterschiedliche Funktionen haben und in einigen Fällen (die Fälle, die sich auf die Absicht der Schnittstelle beziehen) sich nicht darum kümmern, um welches Objekt es sich handelt.

Zum Beispiel könnte ich eine Bewegungsschnittstelle haben. Eine Methode, mit der sich etwas bewegt, und jedes Objekt (Person, Auto, Katze), das die Bewegungsschnittstelle implementiert, kann übergeben und angewiesen werden, sich zu bewegen. Ohne die Methode weiß jeder, welche Art von Klasse es ist.


3

Stellen Sie sich vor, Sie haben ein Produkt namens "Zebra", das durch Plugins erweitert werden kann. Es findet die Plugins, indem es in einem Verzeichnis nach DLLs sucht. Es lädt alle diese DLLs und verwendet Reflection, um alle implementierten Klassen zu findenIZebraPlugin , und ruft dann die Methoden dieser Schnittstelle auf, um mit den Plugins zu kommunizieren.

Dies macht es völlig unabhängig von einer bestimmten Plugin-Klasse - es ist egal, was die Klassen sind. Es ist nur wichtig, dass sie die Schnittstellenspezifikation erfüllen.

Schnittstellen sind eine Möglichkeit, solche Erweiterungspunkte zu definieren. Code, der mit einer Schnittstelle kommuniziert, ist lockerer gekoppelt - tatsächlich ist er überhaupt nicht mit einem anderen spezifischen Code gekoppelt. Es kann mit Plugins interagieren, die Jahre später von Leuten geschrieben wurden, die den ursprünglichen Entwickler noch nie getroffen haben.

Sie könnten stattdessen eine Basisklasse mit virtuellen Funktionen verwenden - alle Plugins würden von der Basisklasse abgeleitet. Dies ist jedoch viel einschränkender, da eine Klasse nur eine Basisklasse haben kann, während sie eine beliebige Anzahl von Schnittstellen implementieren kann.


3

C ++ Erklärung.

Stellen Sie sich eine Schnittstelle als öffentliche Methode Ihrer Klasse vor.

Sie können dann eine Vorlage erstellen, die von diesen öffentlichen Methoden abhängt, um eine eigene Funktion auszuführen (Funktionsaufrufe werden in der öffentlichen Schnittstelle der Klasse definiert). Nehmen wir an, diese Vorlage ist ein Container wie eine Vektorklasse, und die Schnittstelle, von der sie abhängt, ist ein Suchalgorithmus.

Jede Algorithmusklasse, die die Funktionen / Schnittstellen definiert, an die Vector Anrufe tätigt, erfüllt den 'Vertrag' (wie in der ursprünglichen Antwort erläutert). Die Algorithmen müssen nicht einmal derselben Basisklasse angehören. Die einzige Voraussetzung ist, dass die Funktionen / Methoden, von denen der Vektor abhängt (Schnittstelle), in Ihrem Algorithmus definiert sind.

Der Sinn all dessen ist, dass Sie jeden anderen Suchalgorithmus / jede andere Suchklasse angeben können, solange die Schnittstelle bereitgestellt wird, von der Vector abhängt (Blasensuche, sequentielle Suche, Schnellsuche).

Möglicherweise möchten Sie auch andere Container (Listen, Warteschlangen) entwerfen, die denselben Suchalgorithmus wie Vector nutzen, indem sie die Schnittstelle / den Vertrag erfüllen, von der Ihre Suchalgorithmen abhängen.

Dies spart Zeit (OOP-Prinzip 'Code-Wiederverwendung'), da Sie einen Algorithmus einmal anstatt immer wieder spezifisch für jedes neue Objekt schreiben können, das Sie erstellen, ohne das Problem mit einem überwucherten Vererbungsbaum zu komplizieren.

Was das "Verpassen" der Funktionsweise betrifft; Big-Time (zumindest in C ++), da auf diese Weise die meisten Frameworks der Standard TEMPLATE Library funktionieren.

Natürlich ändert sich bei Verwendung von Vererbungs- und abstrakten Klassen die Methodik der Programmierung auf eine Schnittstelle. Das Prinzip ist jedoch dasselbe. Ihre öffentlichen Funktionen / Methoden sind Ihre Klassenschnittstelle.

Dies ist ein großes Thema und eines der Eckpfeiler von Design Patterns.


3

In Java implementieren alle diese konkreten Klassen die CharSequence-Schnittstelle:

CharBuffer, String, StringBuffer, StringBuilder

Diese konkreten Klassen haben keine andere übergeordnete Klasse als Object, daher gibt es nichts, was sie in Beziehung setzt, außer der Tatsache, dass sie jeweils etwas mit Arrays von Zeichen zu tun haben, die solche darstellen oder manipulieren. Beispielsweise können die Zeichen von String nicht geändert werden, sobald ein String-Objekt instanziiert wurde, während die Zeichen von StringBuffer oder StringBuilder bearbeitet werden können.

Jede dieser Klassen ist jedoch in der Lage, die CharSequence-Schnittstellenmethoden geeignet zu implementieren:

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

In einigen Fällen wurden Java-Klassenbibliotheksklassen, die früher String akzeptierten, überarbeitet, um jetzt die CharSequence-Schnittstelle zu akzeptieren. Wenn Sie also eine Instanz von StringBuilder haben, anstatt ein String-Objekt zu extrahieren (was bedeutet, dass eine neue Objektinstanz instanziiert wird), kann es stattdessen einfach den StringBuilder selbst übergeben, während es die CharSequence-Schnittstelle implementiert.

Die anhängbare Schnittstelle, die einige Klassen implementieren, bietet in jeder Situation, in der Zeichen an eine Instanz der zugrunde liegenden konkreten Klassenobjektinstanz angehängt werden können, den gleichen Vorteil. Alle diese konkreten Klassen implementieren die Appendable-Schnittstelle:

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


Es ist schade, dass Schnittstellen CharSequenceso anämisch sind. Ich wünschte, Java und .NET hätten Schnittstellen als Standardimplementierung zugelassen, damit die Benutzer Schnittstellen nicht nur zum Minimieren des Boilerplate-Codes reduzieren. Bei jeder legitimen CharSequenceImplementierung könnte man die meisten Funktionen emulieren String, wenn nur die obigen vier Methoden verwendet werden, aber viele Implementierungen könnten diese Funktionen auf andere Weise viel effizienter ausführen. Leider, auch wenn eine bestimmte Implementierung von CharSequencealles in einem einzigen enthält char[]und viele ausführen könnte ...
Supercat

... Operationen wie indexOfschnell, es gibt keine Möglichkeit, dass ein Anrufer, der mit einer bestimmten Implementierung von nicht vertraut ist CharSequence, sie dazu auffordert, anstatt charAtjedes einzelne Zeichen untersuchen zu müssen.
Supercat

3

Kurzgeschichte: Ein Postbote wird gebeten, nach Hause zu gehen und die Umschläge (Briefe, Dokumente, Schecks, Geschenkkarten, Antrag, Liebesbrief) mit der darauf angegebenen Adresse zu erhalten.

Angenommen, es gibt keine Deckung und bitten Sie den Postboten, nach Hause zu gehen und alle Dinge zu erhalten und an andere Personen zu liefern, kann der Postbote verwirrt werden.

Also besser mit Deckung einwickeln (in unserer Geschichte ist es die Schnittstelle), dann wird er seine Arbeit gut machen.

Jetzt ist es die Aufgabe des Postboten, nur die Umschläge zu erhalten und zu liefern (er würde sich nicht darum kümmern, was sich im Umschlag befindet).

Erstellen Sie eine Art von interface nicht tatsächlichen Typ, implementieren Sie ihn jedoch mit dem tatsächlichen Typ.

Zur Schnittstelle zu erstellen bedeutet, dass Ihre Komponenten erhalten problemlos in den Rest des Codes passen

Ich gebe Ihnen ein Beispiel.

Sie haben die AirPlane-Oberfläche wie folgt.

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

Angenommen, Sie haben Methoden in Ihrer Controller-Klasse von Ebenen wie

parkPlane(Airplane plane)

und

servicePlane(Airplane plane)

in Ihrem Programm implementiert. Ihr Code wird nicht unterbrochen . Ich meine, es muss sich nicht ändern, solange es Argumente akzeptiert wieAirPlane .

Denn es wird jedes Flugzeug trotz tatsächlichen Typ akzeptieren, flyer, highflyr,fighter etc.

Auch in einer Sammlung:

List<Airplane> plane; // Nimm alle deine Flugzeuge.

Das folgende Beispiel verdeutlicht Ihr Verständnis.


Sie haben ein Kampfflugzeug, das es implementiert

public class Fighter implements Airplane {

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

Das Gleiche gilt für HighFlyer und andere Klassen:

public class HighFlyer implements Airplane {

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

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

Denken Sie nun, Ihre Controller-Klassen verwenden AirPlanemehrmals:

Angenommen, Ihre Controller-Klasse ist ControlPlane wie unten.

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

Hier kommt Magie, da Sie Ihre neuen AirPlaneTypinstanzen so viele machen können, wie Sie möchten, und Sie den ControlPlaneKlassencode nicht ändern .

Sie können eine Instanz hinzufügen ...

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

Sie können auch Instanzen zuvor erstellter Typen entfernen.


2

Eine Schnittstelle ist wie ein Vertrag, bei dem Ihre Implementierungsklasse im Vertrag geschriebene Methoden (Schnittstelle) implementieren soll. Da Java keine Mehrfachvererbung bietet, ist "Programmieren auf Schnittstelle" ein guter Weg, um Mehrfachvererbung zu erreichen.

Wenn Sie eine Klasse A haben, die bereits eine andere Klasse B erweitert, aber möchten, dass diese Klasse A auch bestimmten Richtlinien folgt oder einen bestimmten Vertrag implementiert, können Sie dies mit der Strategie "Programmieren auf Schnittstelle" tun.


2

F: - ... "Könnten Sie eine Klasse verwenden, die eine Schnittstelle implementiert?"
A: - Ja.

F: - ... "Wann müssten Sie das tun?"
A: - Jedes Mal, wenn Sie eine Klasse benötigen, die Schnittstellen implementiert.

Hinweis: Wir konnten keine Schnittstelle instanziieren, die nicht von einer Klasse implementiert wurde - True.

  • Warum?
  • Da die Schnittstelle nur Methodenprototypen und keine Definitionen enthält (nur Funktionsnamen, nicht deren Logik)

AnIntf anInst = new Aclass();
// Wir könnten dies nur tun, wenn Aclass AnIntf ​​implementiert.
// anInst hat eine Klassenreferenz.


Hinweis: Jetzt konnten wir verstehen, was passiert ist, wenn Bclass und Cclass dasselbe Dintf implementiert haben.

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

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

Was wir haben: Gleiche Schnittstellenprototypen (Funktionsnamen in der Schnittstelle) und Aufruf verschiedener Implementierungen.

Bibliographie: Prototypen - Wikipedia


1

Das Programmieren auf eine Schnittstelle ermöglicht die nahtlose Änderung der Implementierung eines durch die Schnittstelle definierten Vertrags. Es ermöglicht eine lose Kopplung zwischen Vertrag und spezifischen Implementierungen.

IInterface classRef = new ObjectWhatever()

Sie könnten jede Klasse verwenden, die IInterface implementiert? Wann müssten Sie das tun?

Schauen Sie sich diese SE-Frage als gutes Beispiel an.

Warum sollte die Schnittstelle für eine Java-Klasse bevorzugt werden?

Hat die Verwendung einer Schnittstelle die Leistung beeinträchtigt?

wenn ja wie viel?

Ja. Es wird in Sekundenschnelle einen leichten Leistungsaufwand haben. Wenn Ihre Anwendung jedoch die Implementierung der Schnittstelle dynamisch ändern muss, machen Sie sich keine Sorgen über die Auswirkungen auf die Leistung.

Wie können Sie dies vermeiden, ohne zwei Codebits verwalten zu müssen?

Versuchen Sie nicht, mehrere Implementierungen der Schnittstelle zu vermeiden, wenn Ihre Anwendung diese benötigt. Wenn die Schnittstelle nicht eng mit einer bestimmten Implementierung gekoppelt ist, müssen Sie möglicherweise den Patch bereitstellen, um eine Implementierung in eine andere Implementierung zu ändern.

Ein guter Anwendungsfall: Implementierung des Strategiemusters:

Beispiel aus der Praxis für das Strategiemuster


1

Programm zu einer Schnittstelle ist ein Begriff aus dem GOF-Buch. Ich würde nicht direkt sagen, dass es mit der Java-Schnittstelle zu tun hat, sondern mit echten Schnittstellen. Um eine saubere Schichttrennung zu erreichen, müssen Sie beispielsweise eine gewisse Trennung zwischen Systemen erstellen: Angenommen, Sie hatten eine konkrete Datenbank, die Sie verwenden möchten. Sie würden niemals "auf die Datenbank programmieren", sondern "auf die Speicherschnittstelle programmieren". Ebenso würden Sie niemals "auf einen Webdienst programmieren", sondern auf eine "Client-Schnittstelle" programmieren. Auf diese Weise können Sie die Dinge einfach austauschen.

Ich finde diese Regeln helfen mir:

1 . Wir verwenden eine Java-Schnittstelle, wenn wir mehrere Arten von Objekten haben. Wenn ich nur ein einzelnes Objekt habe, sehe ich den Punkt nicht. Wenn es mindestens zwei konkrete Implementierungen einer Idee gibt, würde ich eine Java-Schnittstelle verwenden.

2 . Wenn Sie, wie oben erwähnt, die Entkopplung von einem externen System (Speichersystem) auf Ihr eigenes System (lokale Datenbank) übertragen möchten, verwenden Sie auch eine Schnittstelle.

Beachten Sie, wie Sie auf zwei Arten überlegen können, wann Sie sie verwenden sollen. hoffe das hilft.


0

Außerdem sehe ich hier viele gute und erklärende Antworten, daher möchte ich hier meinen Standpunkt darlegen, einschließlich einiger zusätzlicher Informationen, die mir bei der Verwendung dieser Methode aufgefallen sind.

Unit Testing

In den letzten zwei Jahren habe ich ein Hobbyprojekt geschrieben und keine Unit-Tests dafür geschrieben. Nachdem ich ungefähr 50.000 Zeilen geschrieben hatte, fand ich heraus, dass es wirklich notwendig wäre, Unit-Tests zu schreiben. Ich habe keine Schnittstellen verwendet (oder sehr sparsam) ... und als ich meinen ersten Unit-Test machte, stellte ich fest, dass es kompliziert war. Warum?

Weil ich viele Klasseninstanzen erstellen musste, die für die Eingabe als Klassenvariablen und / oder Parameter verwendet wurden. Die Tests sehen also eher wie Integrationstests aus (sie müssen ein vollständiges 'Framework' von Klassen erstellen, da alles miteinander verbunden war).

Angst vor Schnittstellen Also habe ich mich für Schnittstellen entschieden. Ich befürchtete, dass ich alle Funktionen überall (in allen verwendeten Klassen) mehrmals implementieren musste. In gewisser Weise ist dies jedoch der Fall, da durch die Verwendung der Vererbung eine erhebliche Reduzierung möglich ist.

Kombination von Schnittstellen und Vererbung Ich habe herausgefunden, dass die Kombination sehr gut zu verwenden ist. Ich gebe ein sehr einfaches Beispiel.

public interface IPricable
{
    int Price { get; }
}

public interface ICar : IPricable

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

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

Auf diese Weise ist das Kopieren von Code nicht erforderlich, obwohl der Vorteil besteht, dass ein Auto als Schnittstelle (ICar) verwendet wird.


0

Beginnen wir zunächst mit einigen Definitionen:

Schnittstelle n. Die Menge aller Signaturen, die durch die Operationen eines Objekts definiert werden, wird als Schnittstelle zum Objekt bezeichnet

Geben Sie n ein. Eine bestimmte Schnittstelle

Ein einfaches Beispiel für eine Schnittstelle , wie oben definiert , würde alle PDO Objektmethoden , wie beispielsweise sein query(), commit(), close()usw., als ein Ganzes, nicht getrennt. Diese Methoden, dh ihre Schnittstelle, definieren den vollständigen Satz von Nachrichten, Anforderungen, die an das Objekt gesendet werden können.

Ein Typ wie oben definiert ist eine bestimmte Schnittstelle. Ich werde die konfektionierten Form Schnittstelle demonstrieren: draw(), getArea(), getPerimeter()etc ..

Wenn ein Objekt des Datenbanktypen ist gemeint , dass es Nachrichten / Anfragen der Datenbankschnittstelle übernimmt, query(), commit()etc .. Objekte können von vielen Arten sein. Sie können ein Datenbankobjekt vom Formtyp haben, solange es seine Schnittstelle implementiert. In diesem Fall wäre dies eine Untertypisierung .

Viele Objekte können von vielen verschiedenen Schnittstellen / Typen sein und diese Schnittstelle unterschiedlich implementieren. Auf diese Weise können wir Objekte ersetzen und auswählen, welches verwendet werden soll. Auch als Polymorphismus bekannt.

Der Client kennt nur die Schnittstelle und nicht die Implementierung.

Also im Grunde der Programmierung eine Schnittstelle würde bedeuten , eine Art der abstrakten Klasse machen wie Shapemit der Schnittstelle nur dann angegeben , dh draw(), getCoordinates(), getArea()etc .. Und haben dann verschiedene konkrete Klassen diese Schnittstellen implementieren wie eine Circle - Klasse, Klasse Square, Triangle - Klasse. Daher programmiere auf eine Schnittstelle keine Implementierung.


0

"Programm zur Schnittstelle" bedeutet, dass der Hardcode nicht richtig bereitgestellt wird. Dies bedeutet, dass Ihr Code erweitert werden sollte, ohne die vorherige Funktionalität zu beeinträchtigen. Nur Erweiterungen, nicht das Bearbeiten des vorherigen Codes.

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.