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?
language-agnostic
oop
interface
Damien
quelle
quelle
Antworten:
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:
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
HouseFly
oder einTelemarketer
oder 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 auchHouseFly
ein 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: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üllen
IPest
- dieser Vertrag ist einfach. Du musst nurBeAnnoying
. In dieser Hinsicht können wir Folgendes modellieren: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 der
pests
Arrays tatsächlich einTelemarketer
Objekt oder einHouseFly
Objekt sein kann.Die
ServeDinner
Methode 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 dieIPest
Benutzeroberfläche nervig zu sein . Auf diese Weise können wir leicht beides habenTelemarketers
undHouseFlys
auf jede ihrer eigenen Arten nerven - es ist uns nurDiningRoom
wichtig, 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.
quelle
BeAnnoying
als 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 ...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 einzelneBeAnnoying()
Methode aufgerufen.Das spezifische Beispiel, das ich den Schülern gegeben habe, ist, dass sie schreiben sollten
anstatt
Diese sehen in einem kurzen Programm genauso aus, aber wenn Sie
myList
100 Mal in Ihrem Programm verwenden, können Sie einen Unterschied feststellen. Die erste Deklaration stellt sicher, dass Sie nur Methoden aufrufenmyList
, die von derList
Schnittstelle definiert wurden (also keineArrayList
spezifischen Methoden). Wenn Sie auf diese Weise auf die Schnittstelle programmiert haben, können Sie später entscheiden, dass Sie sie wirklich benötigenund 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:
Diese Methodendeklaration bindet Sie an zwei konkrete Implementierungen (
ArrayList
undHashMap
). 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.Jetzt spielt es keine Rolle, welche Art von
List
Sie zurückgeben oder welche Art vonMap
Parameter übergeben wird. Änderungen, die Sie innerhalb derdoSomething
Methode vornehmen, zwingen Sie nicht, den aufrufenden Code zu ändern.quelle
Das Programmieren auf eine Schnittstelle sagt: "Ich brauche diese Funktionalität und es ist mir egal, woher sie kommt."
Betrachten Sie (in Java) die
List
Schnittstelle gegenüber denArrayList
undLinkedList
konkreten 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ählenList
(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 dieLinkedList
konkrete Implementierung auswählen (oder eher die Warteschlangenschnittstelle verwenden). Wenn ich weiß, dass ich einen zufälligen Zugriff per Index benötige, würde ich dieArrayList
konkrete Klasse auswählen .quelle
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
quelle
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.
quelle
Sie sollten sich mit Inversion of Control befassen:
In einem solchen Szenario würden Sie Folgendes nicht schreiben:
Sie würden so etwas schreiben:
Dies würde in ein regelbasiertes Setup im
container
Objekt 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:
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:
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.
quelle
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.
quelle
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:
dann könnte ich GoogleSearchProvider, YahooSearchProvider, LiveSearchProvider usw. erstellen.
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
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:
quelle
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.
quelle
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:Wenn Sie eine Schnittstelle erstellen, sagen Sie a
List
. Sie codieren nur hinsichtlich der Definition der Liste oder wasList
in der Realität bedeutet.Sie können jede Art von Implementierung intern verwenden, beispielsweise eine
array
Implementierung. 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 DeklarationList<String> ls = new ArrayList<String>()
in ändernList<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
.quelle
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.
quelle
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).
quelle
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:
und darüber hinaus begann es mit:
Mit anderen Worten, schreiben Sie Ihre Klassen nicht so, dass sie eine
quack()
Methode für Enten und dann einebark()
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()
odermove()
, 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 verwendenquack()
oderbark()
sogar bestimmen möchten, bevor Sie die richtige Nachricht ausgeben, die an das Objekt gesendet werden soll.quelle
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.
quelle
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.
quelle
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
quelle
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:
Stellen Sie sich beispielsweise eine
Person
Klasse vor, die dieFriend
und dieEmployee
Schnittstelle implementiert.Im Zusammenhang mit dem Geburtstag der Person programmieren wir auf die
Friend
Benutzeroberfläche, um zu verhindern, dass die Person wie eine behandelt wirdEmployee
.Im Rahmen der Arbeit der Person programmieren wir auf die
Employee
Schnittstelle, um ein Verwischen der Arbeitsplatzgrenzen zu vermeiden.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
Dog
Klasse, die sowohlFriend
als auch implementiertEmployee
. Dann wechseln wir sichernew Person()
zunew Dog()
. Selbst wenn beide Funktionen Tausende von Codezeilen haben, funktioniert diese einfache Bearbeitung, da wir wissen, dass Folgendes zutrifft:party
verwendet nur dieFriend
Teilmenge vonPerson
.workplace
verwendet nur dieEmployee
Teilmenge vonPerson
.Dog
implementiert sowohl dieFriend
als auch dieEmployee
Schnittstellen.Wenn andererseits programmiert wird
party
oder dagegenworkplace
programmiert wirdPerson
, besteht das Risiko, dass beide einenPerson
spezifischen Code haben. Wenn wir vonPerson
zu wechseln ,Dog
müssen wir den Code durchkämmen, um jedenPerson
spezifischen Code auszulöschen,Dog
der 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.
quelle
Wenn ich eine neue Klasse schreibe
Swimmer
, um die Funktionalität hinzuzufügen,swim()
und ein Objekt der Klasse verwenden muss, sagen wirDog
, und dieseDog
Klasse implementiert eine Schnittstelle,Animal
die deklariertswim()
.Am oberen
Animal
Rand 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 vonSwimmer
Klassen meinen Code gegen die Schnittstelle schreiben möchte, die so weit oben in der Hierarchie liegt, die in diesem Fall einAnimal
Objekt 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.
quelle
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.
quelle
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 finden
IZebraPlugin
, 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.
quelle
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.
quelle
In Java implementieren alle diese konkreten Klassen die CharSequence-Schnittstelle:
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:
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:
quelle
CharSequence
so 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 legitimenCharSequence
Implementierung könnte man die meisten Funktionen emulierenString
, 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 vonCharSequence
alles in einem einzigen enthältchar[]
und viele ausführen könnte ...indexOf
schnell, es gibt keine Möglichkeit, dass ein Anrufer, der mit einer bestimmten Implementierung von nicht vertraut istCharSequence
, sie dazu auffordert, anstattcharAt
jedes einzelne Zeichen untersuchen zu müssen.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.
Angenommen, Sie haben Methoden in Ihrer Controller-Klasse von Ebenen wie
und
in Ihrem Programm implementiert. Ihr Code wird nicht unterbrochen . Ich meine, es muss sich nicht ändern, solange es Argumente akzeptiert wie
AirPlane
.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
Das Gleiche gilt für HighFlyer und andere Klassen:
Denken Sie nun, Ihre Controller-Klassen verwenden
AirPlane
mehrmals:Angenommen, Ihre Controller-Klasse ist ControlPlane wie unten.
Hier kommt Magie, da Sie Ihre neuen
AirPlane
Typinstanzen so viele machen können, wie Sie möchten, und Sie denControlPlane
Klassencode nicht ändern .Sie können eine Instanz hinzufügen ...
Sie können auch Instanzen zuvor erstellter Typen entfernen.
quelle
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.
quelle
Hinweis: Wir konnten keine Schnittstelle instanziieren, die nicht von einer Klasse implementiert wurde - True.
Hinweis: Jetzt konnten wir verstehen, was passiert ist, wenn Bclass und Cclass dasselbe Dintf implementiert haben.
Was wir haben: Gleiche Schnittstellenprototypen (Funktionsnamen in der Schnittstelle) und Aufruf verschiedener Implementierungen.
Bibliographie: Prototypen - Wikipedia
quelle
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.
Schauen Sie sich diese SE-Frage als gutes Beispiel an.
Warum sollte die Schnittstelle für eine Java-Klasse bevorzugt werden?
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.
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
quelle
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.
quelle
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.
Auf diese Weise ist das Kopieren von Code nicht erforderlich, obwohl der Vorteil besteht, dass ein Auto als Schnittstelle (ICar) verwendet wird.
quelle
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
Shape
mit der Schnittstelle nur dann angegeben , dhdraw()
,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.quelle
"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.
quelle