Warum sollte ein Programmierer die Implementierung von der Schnittstelle trennen wollen?

18

Das Brückenentwurfsmuster trennt die Implementierung von der Schnittstelle eines Programms.

Warum ist das vorteilhaft?

David Faux
quelle
Um eine undichte Abstraktion
SK-logic
1
Dies scheint von jedermann bereits verstanden zu werden, aber für neue Zuschauer geht es bei Bridge Design nicht um die Implementierung und Schnittstelle "eines Programms", sondern um Teile, vielleicht sogar kleine Teile eines Programms. Ein ganzes Programm ist eine Sammlung von Schnittstellen und Implementierungen (wobei jede Schnittstelle eine oder mehrere Implementierungen hat). Der erste Satz sollte vielleicht lauten: "Das Bridge-Entwurfsmuster trennt die Schnittstellen von ihren Implementierungen im gesamten Quellcode."
RalphChapin

Antworten:

35

Damit können Sie die Implementierung unabhängig von der Schnittstelle ändern. Dies hilft bei sich ändernden Anforderungen.

Das klassische Beispiel besteht darin, die Speicherimplementierung unter einer Schnittstelle durch etwas Größeres, Besseres, Schnelleres, Kleineres oder ein anderes zu ersetzen, ohne den Rest des Systems ändern zu müssen.

Daniel Pittman
quelle
23

Zusätzlich zu Daniels Antwort können Sie durch die Trennung der Schnittstelle von der Implementierung durch Konzepte wie Polymorphismus mehrere Implementierungen derselben Schnittstelle erstellen, die ähnliche Aktionen auf unterschiedliche Weise ausführen.

Beispielsweise haben viele Sprachen irgendwo in der Standardbibliothek das Konzept eines Streams . Ein Stream enthält Daten für den seriellen Zugriff. Es gibt zwei grundlegende Operationen: Lesen (Laden der nächsten x Anzahl von Bytes aus dem Stream) und Schreiben (Hinzufügen von x Anzahl von Datenbytes zum Stream) und manchmal ein drittes Suchen (Zurücksetzen der "aktuellen Position" des Streams an einen neuen Ort).

Das ist ein recht einfaches Konzept, aber denken Sie an all die Dinge, die Sie damit machen könnten. Am naheliegendsten ist die Interaktion mit Dateien auf der Disc. Mit einem Dateistream können Sie Daten aus einer Datei lesen oder darauf schreiben. Was aber, wenn Sie stattdessen Daten über eine Netzwerkverbindung senden möchten?

Wenn Sie sich direkt auf Implementierungen verlassen würden, müssten Sie zwei völlig unterschiedliche Routinen schreiben, um dieselben Daten in einer Datei zu speichern oder über das Netzwerk zu senden. Wenn Sie jedoch über eine Stream-Schnittstelle verfügen, können Sie zwei verschiedene Implementierungen davon erstellen ( FileStreamund NetworkStream), in denen die spezifischen Details des Sendens der Daten zusammengefasst sind. Anschließend müssen Sie nur den Code schreiben, der das einmalige Speichern einer Datei behandelt . Plötzlich sind Ihre SaveToFileund SendOverNetworkRoutinen viel einfacher: Sie richten einfach einen Stream des entsprechenden Typs ein und übergeben ihn an die SaveDataRoutine, die eine Stream-Schnittstelle akzeptiert - es muss sich nicht darum kümmern, welchen Typ er hat, solange er den ausführt Schreibvorgang - und speichert die Daten im Stream.

Das bedeutet auch, dass Sie Ihr Datenformat nicht an mehreren Stellen ändern müssen, wenn es sich ändert. Wenn Sie Ihren Datenspeicherungscode in einer Routine zentralisieren, die einen Stream aufnimmt, ist dies der einzige Ort, an dem er aktualisiert werden muss. Sie können also nicht versehentlich einen Fehler auslösen, indem Sie nur einen Ort ändern, wenn Sie beide ändern müssen. Die Trennung von Schnittstellen und Implementierungen und die Verwendung von Polymorphismus führt zu Code, der einfacher zu lesen und zu verstehen ist und der mit geringerer Wahrscheinlichkeit Fehler aufweist.

Mason Wheeler
quelle
1
Dies ist eines der stärksten Merkmale von OOP. Ich liebe es einfach ...
Radu Murzea
1
Wie ist diese andere Form mit einfachen Schnittstellen?
Vainolo
@Vainolo: Es hilft bei der Wiederverwendung von Code. Selbst wenn Sie mehrere Arten von Streams haben, haben sie alle die gleichen Funktionen. Wenn Sie mit einer IStreamSchnittstelle beginnen, müssen Sie den gesamten Funktionsumfang für jeden Stream neu erfinden. Wenn Sie jedoch mit einer abstrakten Basis-Stream-Klasse beginnen, kann sie den gesamten gemeinsamen Status und die gesamte Funktionalität enthalten und dann die einzelnen Features von den Nachkommen implementieren lassen.
Mason Wheeler
2
@RaduMurzea Dies ist nicht spezifisch für OOP. Mit Typklassen können Sie dasselbe auf eine Art und Weise tun, die von OOP völlig unabhängig ist.
Wes
@Wes genau, wollte nur dasselbe sagen, und dann habe ich deinen Kommentar gelesen.
Jhegedus
4

Sie haben hier wirklich zwei sehr unterschiedliche Fragen, obwohl sie miteinander zusammenhängen.

Die allgemeinere Frage ist die im Titel, warum Sie die Schnittstelle von der Implementierung im Allgemeinen trennen sollten. Die zweite Frage ist, warum das Brückenmuster nützlich ist. Sie hängen zusammen, weil das Brückenmuster eine bestimmte Art der Trennung der Schnittstelle von der Implementierung darstellt, die auch einige bestimmte andere Konsequenzen hat.

Die allgemeine Frage ist für jeden Programmierer von entscheidender Bedeutung. Dies verhindert, dass sich Änderungen in einem Programm überall verbreiten. Ich kann mir nicht vorstellen, dass Menschen programmieren können, ohne dies zu benutzen.

Wenn Sie eine einfache Additionsanweisung in einer Programmiersprache schreiben, ist dies bereits eine Abstraktion (auch wenn sie keine Operatorüberladung zum Hinzufügen von Matrizen oder dergleichen verwendet), die eine ganze Reihe anderer Codes durchläuft, bevor sie schließlich ausgeführt wird auf einem Stromkreis in Ihrem Computer. Wenn es keine Trennung der Schnittstelle (z. B. "3 + 5") von der Implementierung (eine Reihe von Maschinencode) gäbe, müssten Sie Ihren Code jedes Mal ändern, wenn sich die Implementierung ändert (als wollten Sie auf einem ausführen) neuer Prozessor).

Selbst innerhalb einer einfachen CRUD-Anwendung ist jede Methodensignatur im weiteren Sinne die Schnittstelle zu ihrer Implementierung.

Alle diese Abstraktionsarten haben dasselbe grundlegende Ziel: Lassen Sie den aufrufenden Code seine Absicht so abstrakt wie möglich ausdrücken, damit der Implementierer so viele Informationen wie nötig erhält. Dies stellt die minimal mögliche Kopplung zwischen ihnen bereit und begrenzt den Welligkeitseffekt, wenn Code so weit wie möglich geändert werden muss.

Es klingt einfach, aber in der Praxis wird es kompliziert.

Das Brückenmuster ist eine spezielle Methode, um bestimmte Implementierungsbits in Schnittstellen zu unterteilen. Das Klassendiagramm des Musters ist informativer als die Beschreibung. Es ist eher eine Art, steckbare Module als eine Bridge zu haben, aber sie haben sie Bridge genannt, da sie häufig dort verwendet wird, wo die Module vor der Schnittstelle erstellt wurden. Das Erstellen einer gemeinsamen Schnittstelle für ähnliche vorhandene Implementierungen überbrückt also den Unterschied und ermöglicht es Ihnen, mit jeder der Implementierungen zu arbeiten.

Angenommen, Sie möchten ein Add-On für ein Textverarbeitungsprogramm schreiben, möchten jedoch, dass es auf mehreren Textverarbeitungsprogrammen funktioniert. Sie können eine Schnittstelle erstellen, die die von Ihnen benötigte textverarbeitungsbasierte Funktionalität abstrahiert (und die von jeder Textverarbeitung implementiert werden muss, da Sie diese nicht ändern können), und einen Implementierer dieser Schnittstelle für jede zu unterstützende Textverarbeitung. Dann kann Ihre Anwendung diese Schnittstelle aufrufen und sich nicht um die Details der einzelnen Textverarbeitungsprogramme kümmern.

Tatsächlich ist es etwas detaillierter, da jede Klasse möglicherweise eine Klassenhierarchie enthält (es gibt also möglicherweise nicht nur ein abstraktes Textverarbeitungsprogramm, sondern ein abstraktes Dokument, eine abstrakte Textauswahl usw. mit konkreten Implementierungen für jede Klasse) die gleiche Idee.

Es ist ein bisschen wie eine Fassade, nur dass in diesem Fall die Abstraktionsschicht darauf abzielt, die gleiche Schnittstelle für mehrere zugrunde liegende Systeme bereitzustellen.

Dies hängt mit Inversion Of Control zusammen, da der konkrete Implementierer an Methoden oder Konstruktoren übergeben wird und die tatsächlich aufgerufene Implementierung bestimmt.

bA
quelle