Grundlegendes zum Programmieren an einer Schnittstelle

29

Ich bin oft auf den Begriff "Programmieren auf eine Schnittstelle anstatt auf eine Implementierung" gestoßen, und ich denke, ich verstehe irgendwie, was das bedeutet. Aber ich möchte sicherstellen, dass ich die Vorteile und möglichen Implementierungen verstehe.

"Programmieren auf eine Schnittstelle" bedeutet, dass man sich, wenn möglich, auf eine abstraktere Ebene einer Klasse (eine Schnittstelle, eine abstrakte Klasse oder manchmal eine Art Superklasse) beziehen sollte, anstatt auf eine konkrete Implementierung zu verweisen.

Ein gängiges Beispiel in Java ist die Verwendung von:

List myList = new ArrayList();statt ArrayList myList = new ArrayList();.

Ich habe dazu zwei Fragen:

  1. Ich möchte sicherstellen, dass ich die Hauptvorteile dieses Ansatzes verstehe. Ich denke, die Vorteile liegen hauptsächlich in der Flexibilität. Das Deklarieren eines Objekts als Referenz auf höherer Ebene anstelle einer konkreten Implementierung ermöglicht mehr Flexibilität und Wartbarkeit während des gesamten Entwicklungszyklus und des gesamten Codes. Ist das richtig? Ist Flexibilität der Hauptvorteil?

  2. Gibt es mehr Möglichkeiten, eine Schnittstelle zu programmieren? Oder ist "eine Variable als Schnittstelle anstatt als konkrete Implementierung deklarieren" die einzige Implementierung dieses Konzepts?

Ich spreche nicht über das Java-Konstrukt-Interface . Ich spreche über das OO-Prinzip "Programmieren auf eine Schnittstelle, keine Implementierung". In diesem Prinzip bezieht sich die Welt "Schnittstelle" auf jeden "Supertyp" einer Klasse - eine Schnittstelle, eine abstrakte Klasse oder eine einfache Superklasse, die abstrakter und weniger konkret ist als konkretere Unterklassen.

Aviv Cohn
quelle
mögliches Duplikat von Warum sind Schnittstellen nützlich?
gnat
1
Diese Antwort gibt Ihnen ein leicht zu verstehendes Beispiel für programmers.stackexchange.com/a/314084/61852
Tulains Córdova

Antworten:

44

"Programmieren auf eine Schnittstelle" bedeutet, dass man sich, wenn möglich, auf eine abstraktere Ebene einer Klasse (eine Schnittstelle, eine abstrakte Klasse oder manchmal eine Art Superklasse) beziehen sollte, anstatt auf eine konkrete Implementierung zu verweisen.

Das ist nicht richtig . Oder zumindest ist es nicht ganz richtig.

Der wichtigere Punkt ergibt sich aus der Perspektive der Programmgestaltung. Hier „Programmierung mit einer Schnittstelle“ bedeutet Fokussierung Ihr Design auf , was der Code tut, nicht , wie sie es tut. Dies ist eine wichtige Unterscheidung, die Ihr Design in Richtung Korrektheit und Flexibilität treibt.

Die Hauptidee ist, dass sich Domänen viel langsamer ändern als Software. Angenommen, Sie haben eine Software, mit der Sie Ihre Einkaufsliste verwalten können. In den 80er Jahren arbeitete diese Software gegen eine Befehlszeile und einige flache Dateien auf einer Diskette. Dann hast du eine Benutzeroberfläche. Dann stellen Sie die Liste vielleicht in die Datenbank. Später ist es vielleicht in die Cloud oder auf Mobiltelefone oder die Facebook-Integration umgezogen.

Wenn Sie Ihren Code speziell für die Implementierung (Disketten und Befehlszeilen) entworfen hätten, wären Sie auf Änderungen schlecht vorbereitet. Wenn Sie Ihren Code um die Benutzeroberfläche herum entworfen haben (Manipulieren einer Einkaufsliste), kann die Implementierung frei geändert werden.

Telastyn
quelle
Danke für die Antwort. Nach dem zu urteilen, was Sie geschrieben haben, verstehe ich, was "Programmieren an einer Schnittstelle" bedeutet und welche Vorteile es hat. Ich habe jedoch eine Frage: Das gebräuchlichste konkrete Beispiel für dieses Konzept lautet: Wenn Sie einen Verweis auf ein Objekt erstellen, legen Sie den Verweistyp auf den von diesem Objekt implementierten Schnittstellentyp (oder die von diesem Objekt geerbte Oberklasse) fest, anstatt den Verweistyp festzulegen der Objekttyp. (Aka, List myList = new ArrayList()statt ArrayList myList = new ArrayList(). (Frage steht im nächsten Kommentar)
Aviv Cohn
Meine Frage lautet: Können Sie mir weitere Beispiele für Stellen im Code der realen Welt geben, an denen das Prinzip "Programmieren an einer Schnittstelle" stattfindet? Anders als das übliche Beispiel, das ich im letzten Kommentar beschrieben habe?
Aviv Cohn
6
NO . List/ ArrayListIst nicht das, was ich überhaupt spreche. Es ist eher so, als ob Sie ein EmployeeObjekt bereitstellen und nicht die verknüpften Tabellen, die Sie zum Speichern eines Mitarbeiterdatensatzes verwenden. Oder Sie stellen eine Schnittstelle zum Durchlaufen von Titeln zur Verfügung, ohne sich darum zu kümmern, ob diese Titel gemischt oder auf einer CD gespeichert sind oder aus dem Internet gestreamt werden. Sie sind nur eine Abfolge von Liedern.
Telastyn
Würden Sie sagen, dass "Programmieren an einer Schnittstelle, nicht an einer Implementierung" ein Prinzip ist, das das Abstraction OO-Prinzip ausdrückt?
Aviv Cohn
1
@AvivCohn Ich würde SpringFramework als gutes Beispiel vorschlagen. Alles in Spring kann erweitert oder angepasst werden, indem Sie die Schnittstellen und das Hauptverhalten von Springs selbst implementieren. Die Funktionen funktionieren weiterhin wie erwartet ... oder im Falle von Anpassungen wie erwartet. Das Programmieren auf eine Schnittstelle ist auch die beste Strategie zum Entwerfen von Integrationsframeworks . Auch hier macht Spring mit seiner Federintegration. Zur Entwurfszeit wird mit UML eine Schnittstelle programmiert . Oder es ist was ich tun würde. Ziehe mich von dem ab, was es macht, und konzentriere mich darauf, was zu tun ist.
Laiv
18

Mein Verständnis von "Programmieren an einer Schnittstelle" unterscheidet sich von dem, was die Frage oder die anderen Antworten nahelegen. Das heißt nicht, dass mein Verständnis richtig ist oder dass die Dinge in den anderen Antworten keine guten Ideen sind, nur, dass sie nicht das sind, woran ich denke, wenn ich diesen Begriff höre.

Das Programmieren auf einer Schnittstelle bedeutet, dass Sie, wenn Ihnen eine Programmierschnittstelle präsentiert wird (sei es eine Klassenbibliothek, eine Reihe von Funktionen, ein Netzwerkprotokoll oder irgendetwas anderes), weiterhin nur Dinge verwenden, die von der Schnittstelle garantiert werden. Möglicherweise haben Sie Kenntnisse über die zugrunde liegende Implementierung (Sie haben sie möglicherweise geschrieben), sollten diese Kenntnisse jedoch niemals verwenden.

Angenommen, die API bietet Ihnen einen undurchsichtigen Wert, der ein "Handle" für etwas Internes ist. Ihr Wissen könnte Ihnen sagen, dass dieses Handle wirklich ein Zeiger ist, und Sie könnten es dereferenzieren und auf einen Wert zugreifen, der es Ihnen möglicherweise ermöglicht, eine Aufgabe, die Sie erledigen möchten, auf einfache Weise zu erledigen. Die Benutzeroberfläche bietet Ihnen diese Option jedoch nicht. Es ist Ihr Wissen über die jeweilige Implementierung, die dies tut.

Das Problem dabei ist, dass es eine starke Kopplung zwischen Ihrem Code und der Implementierung schafft, genau das, was die Schnittstelle verhindern sollte. Je nach Politik kann dies bedeuten, dass die Implementierung nicht mehr geändert werden kann, da dies Ihren Code beschädigen würde, oder dass Ihr Code sehr zerbrechlich ist und bei jedem Upgrade oder jeder Änderung der zugrunde liegenden Implementierung unterbrochen wird.

Ein großes Beispiel hierfür sind Programme, die für Windows geschrieben wurden. Die WinAPI ist eine Schnittstelle, aber viele Leute verwendeten Tricks, die aufgrund der speziellen Implementierung in Windows 95 funktionierten. Diese Tricks haben ihre Programme möglicherweise schneller gemacht oder es ihnen ermöglicht, Dinge mit weniger Code zu tun, als dies sonst nötig gewesen wäre. Diese Tricks bedeuteten aber auch, dass das Programm unter Windows 2000 abstürzen würde, da die API dort anders implementiert wurde. Wenn das Programm wichtig genug wäre, könnte Microsoft die Implementierung tatsächlich etwas hacken, damit das Programm weiterhin funktioniert, aber die Kosten dafür erhöhen die Komplexität (mit allen daraus resultierenden Problemen) des Windows-Codes. Dies macht den Wine-Leuten auch das Leben besonders schwer, da sie versuchen, auch die WinAPI zu implementieren, sich jedoch nur auf die Dokumentation beziehen können,

Sebastian Redl
quelle
Das ist ein guter Punkt, und ich höre das in bestimmten Zusammenhängen sehr oft.
Telastyn
Ich weiß, worauf du hinauswillst. Lassen Sie mich also sehen, ob ich das, was Sie sagen, an die allgemeine Programmierung anpassen kann: Nehmen wir an, ich habe eine Klasse (Klasse A), die die Funktionalität der abstrakten Klasse B verwendet. Die Klassen C und D erben die Klasse B - sie bieten eine konkrete Implementierung dafür Klasse soll tun. Wenn Klasse A direkt Klasse C oder D verwendet, wird dies als "Programmierung für eine Implementierung" bezeichnet, was keine sehr flexible Lösung ist. Wenn Klasse A jedoch einen Verweis auf Klasse B verwendet, der später auf die C-Implementierung oder die D-Implementierung festgelegt werden kann, werden die Dinge flexibler und wartbarer. Ist das richtig?
Aviv Cohn
Wenn dies korrekt ist, lautet meine Frage: Gibt es konkretere Beispiele für die Programmierung auf eine Schnittstelle als das übliche Beispiel "Verwenden einer Schnittstellenreferenz anstelle einer konkreten Klassenreferenz"?
Aviv Cohn
2
@AvivCohn Ein bisschen spät in dieser Antwort, aber ein konkretes Beispiel ist das World Wide Web. Während der Browser-Kriege (IE 4-Ära) wurden Websites nicht nach den Vorgaben geschrieben, sondern nach den Macken einiger Browser (Netscape oder IE). Dies war im Grunde die Programmierung der Implementierung anstelle der Schnittstelle.
Sebastian Redl
9

Ich kann nur über meine persönlichen Erfahrungen sprechen, da mir dies auch nie offiziell beigebracht wurde.

Dein erster Punkt ist richtig. Die gewonnene Flexibilität beruht darauf, dass Implementierungsdetails der konkreten Klasse nicht versehentlich aufgerufen werden können, wenn sie nicht aufgerufen werden sollten.

Stellen Sie sich zum Beispiel eine ILoggerSchnittstelle vor, die derzeit als konkrete LogToEmailLoggerKlasse implementiert ist . Die LogToEmailLoggerKlasse macht alle ILoggerMethoden und Eigenschaften verfügbar, verfügt jedoch zufällig über eine implementierungsspezifische Eigenschaft sysAdminEmail.

Wenn Ihr Logger in Ihrer Anwendung verwendet wird, sollte es nicht das Problem des konsumierenden Codes sein, den einzurichten sysAdminEmail. Diese Eigenschaft sollte während der Einrichtung des Loggers festgelegt und vor der Welt verborgen werden.

Wenn Sie gegen die konkrete Implementierung codieren, können Sie die Implementierungseigenschaft versehentlich festlegen, wenn Sie den Logger verwenden. Jetzt ist Ihr Anwendungscode fest mit Ihrem Logger verbunden. Wenn Sie zu einem anderen Logger wechseln, müssen Sie zuerst Ihren Code vom Original entkoppeln.

In diesem Sinne löst die Codierung auf eine Schnittstelle die Kopplung .

Zum zweiten Punkt: Ein weiterer Grund, den ich für das Codieren in eine Schnittstelle gesehen habe, besteht darin, die Komplexität des Codes zu verringern.

Zum Beispiel stelle ich mir ein Spiel mit den folgenden Schnittstellen I2DRenderable, I3DRenderable, IUpdateable. Es ist nicht ungewöhnlich, dass einzelne Spielkomponenten sowohl 2D- als auch 3D-darstellbare Inhalte enthalten. Andere Komponenten können nur 2D und andere nur 3D sein.

Wenn 2D-Rendering von einem Modul ausgeführt wird, ist es sinnvoll, eine Sammlung von I2DRenderables zu verwalten. Es spielt keine Rolle, ob die Objekte in seiner Sammlung auch I3DRenderableoder IUpdatebleals andere Module für die Behandlung dieser Aspekte der Objekte verantwortlich sind.

Durch das Speichern der darstellbaren Objekte als Liste von wird I2DRenderabledie Komplexität der Darstellungsklasse gering gehalten. Die 3D-Rendering- und Aktualisierungslogik spielt keine Rolle. Daher können und sollten diese Aspekte der untergeordneten Objekte ignoriert werden.

In diesem Sinne wird durch das Codieren in eine Schnittstelle die Komplexität gering gehalten, indem Bedenken isoliert werden .

MetaFight
quelle
4

Es gibt vielleicht zwei Verwendungszwecke des hier verwendeten Wortes Interface. Die Schnittstelle, auf die Sie sich in Ihrer Frage hauptsächlich beziehen, ist eine Java-Schnittstelle . Dies ist speziell ein Java-Konzept, im Allgemeinen eine Programmiersprachenschnittstelle.

Ich würde sagen, dass die Programmierung einer Schnittstelle ein umfassenderes Konzept ist. Die mittlerweile beliebten REST-APIs, die für viele Websites verfügbar sind, sind ein weiteres Beispiel für das umfassendere Konzept der Programmierung einer Schnittstelle auf einer höheren Ebene. Indem Sie eine Ebene zwischen dem Innenleben Ihres Codes und der Außenwelt (Personen im Internet, anderen Programmen, selbst anderen Teilen desselben Programms) erstellen, können Sie alles in Ihrem Code ändern, solange Sie nichts an der Außenwelt ändern erwartet, wo dies durch eine Schnittstelle oder einen Vertrag definiert ist, den Sie einhalten möchten.

Das gibt Ihnen dann die Flexibilität, Ihren internen Code umzugestalten, während Sie nicht alle anderen Dinge erklären müssen, die von seiner Schnittstelle abhängen.

Dies bedeutet auch, dass Ihr Code stabiler sein sollte. Wenn Sie sich an die Benutzeroberfläche halten, sollten Sie den Code anderer Benutzer nicht beschädigen. Wenn Sie die Benutzeroberfläche wirklich ändern müssen, können Sie eine neue Hauptversion (1.abc bis 2.xyz) der API freigeben, die anzeigt, dass Änderungen an der Benutzeroberfläche der neuen Version vorgenommen wurden.

Wie @Doval in den Kommentaren zu dieser Antwort ausführt, gibt es auch Fehlerlokalitäten. Ich denke, dass alles auf die Verkapselung hinausläuft. So wie Sie es für Objekte in einem objektorientierten Entwurf verwenden würden, ist dieses Konzept auch auf einer höheren Ebene nützlich.

Encaitar
quelle
1
Ein Vorteil, der normalerweise übersehen wird, ist die Fehlerlokalität. Angenommen, Sie benötigen eine Map und implementieren sie mithilfe eines Binärbaums. Damit dies funktioniert, müssen die Schlüssel eine gewisse Reihenfolge aufweisen, und Sie müssen die Invariante beibehalten, dass sich Schlüssel, die "kleiner" als der Schlüssel des aktuellen Knotens sind, im linken Teilbaum befinden, während Schlüssel, die "größer als" sind, aktiviert sind der richtige Teilbaum. Wenn Sie die Implementierung der Karte hinter einer Oberfläche verbergen und eine Kartensuche fehlschlägt, wissen Sie, dass sich der Fehler im Kartenmodul befinden muss . Wenn es aufgedeckt ist, könnte der Fehler irgendwo im Programm sein. Für mich ist das der Hauptvorteil.
Doval
4

Eine echte Analogie könnte helfen:

Ein Netzstecker in eine Schnittstelle.
Ja; das dreipolige Ding am Ende des Netzkabels von Ihrem Fernseher, Radio, Staubsauger, Waschmaschine usw.

Von Geräten, die Hauptstecker hat (dh die Arbeitsgeräte „hat einen Netzstecker“ interface) kann in behandelt werden genau die gleiche Art und Weise; Sie können alle an eine Wandsteckdose angeschlossen werden und über diese Steckdose Strom beziehen.

Was jedes einzelne Gerät macht, ist völlig anders. Sie werden es nicht weit bringen, Ihre Teppiche mit dem Fernseher zu reinigen, und die meisten Leute schauen nicht auf ihre Waschmaschine, um sich zu unterhalten. Alle diese Geräte haben jedoch das gemeinsame Verhalten, in eine Steckdose eingesteckt zu werden.

Das bekommen Sie mit Interfaces.
Einheitliches Verhalten, das von vielen verschiedenen Objektklassen ausgeführt werden kann , ohne dass die Komplikationen der Vererbung erforderlich sind.

Phill W.
quelle
1

Der Begriff "Programmierung an einer Schnittstelle" ist vieldeutig. Schnittstelle in der Softwareentwicklung ist ein sehr verbreitetes Wort. So erkläre ich den Junior-Entwicklern, die ich im Laufe der Jahre ausgebildet habe, das Konzept.

In der Software-Architektur gibt es eine Vielzahl natürlicher Grenzen. Allgemeine Beispiele schließen ein

  • Die Netzwerkgrenze zwischen Client- und Serverprozessen
  • Die API-Grenze zwischen einer Anwendung und einer Drittanbieter-Bibliothek
  • Interne Codegrenze zwischen verschiedenen Geschäftsbereichen innerhalb des Programms

Was zählt, ist, dass, wenn diese natürlichen Grenzen existieren, sie identifiziert werden und der Vertrag darüber, wie sich diese Grenze verhält, spezifiziert wird. Sie testen Ihre Software nicht danach, ob sich die "andere Seite" verhält, sondern ob Ihre Interaktionen mit der Spezifikation übereinstimmen.

Die Konsequenzen daraus sind:

  • Externe Komponenten können ausgetauscht werden, solange sie die Spezifikation umsetzen
  • Natürlicher Punkt für Komponententests zur Überprüfung des korrekten Verhaltens
  • Integrationstests werden wichtig - War die Spezifikation nicht eindeutig?
  • Als Entwickler haben Sie eine kleinere Problematik, wenn Sie an einer bestimmten Aufgabe arbeiten

Ein Großteil davon kann sich auf Klassen und Schnittstellen beziehen, es ist jedoch ebenso wichtig zu wissen, dass es sich auch auf Datenmodelle, Netzwerkprotokolle und allgemeiner auf die Arbeit mit mehreren Entwicklern bezieht

Michael Shaw
quelle