Adapter-Muster
Teil des Abschnitts Objektorientierte Programmierung der Dart-Journey von Coddy — Lektion 95 von 110.
Das Adapter-Muster ermöglicht es inkompatiblen Schnittstellen, zusammenzuarbeiten. Es fungiert als Brücke zwischen zwei Klassen, die andernfalls nicht zusammenarbeiten könnten, indem es eine bestehende Klasse mit einer neuen Schnittstelle umhüllt, die Clients erwarten. Dies ist besonders nützlich, wenn Drittanbieter-Bibliotheken oder Legacy-Code in Ihre Anwendung integriert werden.
Stellen Sie sich vor, Sie haben ein System, das mit JSON-Daten arbeitet, aber Sie müssen eine Komponente integrieren, die nur XML ausgibt. Anstatt eine der beiden Klassen zu ändern, erstellen Sie einen Adapter, der zwischen ihnen übersetzt:
// Vorhandene Schnittstelle, die Ihr System erwartet
abstract class JsonData {
String getJson();
}
// Legacy-Klasse mit inkompatibler Schnittstelle
class XmlService {
String getXml() {
return '<data><name>John</name></data>';
}
}
// Der Adapter macht XmlService mit JsonData kompatibel
class XmlToJsonAdapter implements JsonData {
final XmlService _xmlService;
XmlToJsonAdapter(this._xmlService);
@override
String getJson() {
String xml = _xmlService.getXml();
// XML in JSON umwandeln (vereinfacht)
return '{"name": "John"}';
}
}
void main() {
var xmlService = XmlService();
var adapter = XmlToJsonAdapter(xmlService);
print(adapter.getJson()); // {"name": "John"}
}Der XmlToJsonAdapter umschließt den XmlService und implementiert das JsonData-Interface. Der Client-Code arbeitet mit dem vertrauten JsonData-Interface, ohne zu wissen, dass die Daten ursprünglich aus einer XML-Quelle stammten. Der Adapter übernimmt die gesamte Übersetzung intern.
Verwenden Sie das Adapter-Muster, wenn Sie eine bestehende Klasse verwenden möchten, deren Schnittstelle jedoch nicht Ihren Anforderungen entspricht, oder wenn Sie eine wiederverwendbare Klasse erstellen müssen, die mit Klassen mit inkompatiblen Schnittstellen zusammenarbeitet.
Aufgabe
EinfachLassen Sie uns ein Media-Player-Adaptersystem bauen! Stellen Sie sich vor, Sie haben ein modernes Audiosystem, das erwartet, dass alle Medienquellen Audio über eine Standardschnittstelle bereitstellen, aber Sie müssen einen alten Plattenspieler integrieren, der eine völlig andere Art der Tonerzeugung hat.
Sie werden zwei Dateien erstellen, um Ihre Implementierung des Adapter-Musters zu organisieren:
media_adapter.dart: Diese Datei enthält Ihre Interface- und Adapter-Klassen. Definieren Sie eine abstrakte KlasseAudioSource, die Ihr modernes System erwartet – sie sollte eine MethodeString playAudio()haben, die die Audioausgabe als String zurückgibt. Erstellen Sie dann eine KlasseVinylPlayer, die das Altgerät repräsentiert – sie hat ein FeldtrackNameund eine MethodeString spinRecord(), die*crackle* Playing vinyl: [trackName] *crackle*zurückgibt. Erstellen Sie schließlich einenVinylAdapter, derAudioSourceimplementiert und einenVinylPlayerkapselt. Die MethodeplayAudio()des Adapters sollte die MethodespinRecord()des Plattenspielers aufrufen und das Ergebnis zurückgeben – so werden die inkompatiblen Schnittstellen überbrückt.main.dart: Importieren Sie Ihre Media-Adapter-Datei und demonstrieren Sie, wie der Adapter den alten Plattenspieler mit Code kompatibel macht, der das moderneAudioSource-Interface erwartet. Erstellen Sie einenVinylPlayermit dem TracknamenBohemian Rhapsody. Erstellen Sie dann einenVinylAdapter, der diesen Plattenspieler kapselt. Speichern Sie den Adapter in einer Variablen vom TypAudioSource, um zu beweisen, dass er der erwarteten Schnittstelle entspricht. Rufen SieplayAudio()auf Ihrer Audioquelle auf und geben Sie das Ergebnis aus.
Das Schöne an diesem Muster ist, dass Ihr Hauptcode mit der Standard-Schnittstelle AudioSource arbeitet – er muss nicht wissen, dass hinter den Kulissen ein alter Plattenspieler die eigentliche Arbeit erledigt!
Erwartete Ausgabe:
*crackle* Playing vinyl: Bohemian Rhapsody *crackle*Spickzettel
Das Adapter-Muster ermöglicht es inkompatiblen Schnittstellen, zusammenzuarbeiten, indem es als Brücke zwischen zwei Klassen fungiert, die andernfalls nicht kooperieren könnten.
Der Adapter umschließt eine bestehende Klasse mit einer neuen Schnittstelle, die Clients erwarten, was ihn nützlich macht, wenn Drittanbieter-Bibliotheken oder Legacy-Code integriert werden.
Grundstruktur
// Ziel-Schnittstelle, die Clients erwarten
abstract class JsonData {
String getJson();
}
// Bestehende Klasse mit inkompatibler Schnittstelle
class XmlService {
String getXml() {
return '<data><name>John</name></data>';
}
}
// Adapter macht XmlService kompatibel mit JsonData
class XmlToJsonAdapter implements JsonData {
final XmlService _xmlService;
XmlToJsonAdapter(this._xmlService);
@override
String getJson() {
String xml = _xmlService.getXml();
// XML in JSON umwandeln
return '{"name": "John"}';
}
}Verwendung
void main() {
var xmlService = XmlService();
var adapter = XmlToJsonAdapter(xmlService);
print(adapter.getJson()); // {"name": "John"}
}Der Adapter implementiert die Ziel-Schnittstelle, während er die inkompatible Klasse umschließt. Der Client-Code arbeitet mit der vertrauten Schnittstelle, ohne die zugrunde liegende Implementierung zu kennen.
Wann man es verwendet: Wenn Sie eine bestehende Klasse verwenden müssen, deren Schnittstelle jedoch nicht Ihren Anforderungen entspricht, oder wenn Sie eine wiederverwendbare Klasse erstellen, die mit Klassen mit inkompatiblen Schnittstellen zusammenarbeitet.
Probier es selbst
import 'media_adapter.dart';
void main() {
// TODO: Erstelle einen VinylPlayer mit dem Track-Namen "Bohemian Rhapsody"
// TODO: Erstelle einen VinylAdapter, der den Vinyl-Player umschließt
// TODO: Speichere den Adapter in einer Variable vom Typ AudioSource
// TODO: Rufe playAudio() auf deiner Audioquelle auf und gib das Ergebnis aus
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienBibliotheken & ImportsEinführung in die OOPKlassen vs. ObjekteDas this-SchlüsselwortMethodenInstanzvariablenKonstruktor-GrundlagenWiederholung – Einfacher Taschenrechner4Null-Sicherheit
Einführung in Null-SicherheitNullable vs. Non-NullableDie ? und ! OperatorenLate-Schlüsselwort & Null-SicherheitNull-aware OperatorenNull-Sicherheit in KlassenZusammenfassung – Benutzerprofil-System7Abstrakte Klassen & Interfaces
Abstrakte KlassenAbstrakte MethodenInterfaces in DartImplizite InterfacesImplementieren vs. ErweiternMehrfache InterfacesRückblick – Shape Calculator10Collections & Generics
Überblick über List, Set und MapTypsichere CollectionsGenerische KlassenGenerische MethodenGenerische ConstraintsIterable & IteratorZusammenfassung – Generische Speicherung13Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungExtension MethodsCallable ClassesSealed Classes (Dart 3)Records (Dart 3)Patterns & Matching (3.0)Enums mit Methoden16Projekt: Bibliotheksverwaltung
ProjektübersichtBuch- und Benutzerklassen2Konstruktoren in Dart
Standard-KonstruktorBenannte KonstruktorenInitialisierungslistenKonstante KonstruktorenFactory-KonstruktorenWeiterleitende KonstruktorenZusammenfassung – Shape Builder5Kapselung
Öffentliche vs. private MemberDie _ Präfix-KonventionSichtbarkeit auf Library-EbeneGetter & Setter im DetailInformation HidingRückblick – Schülerdatensätze8Mixins
Einführung in MixinsMixins erstellenMehrere Mixins verwendenDas on-Schlüsselwort in MixinsMixin vs. VererbungMixin vs. InterfaceZusammenfassung - Tiersystem11Spezielle Methoden
toString() überschreibenhashCode & == überschreibenComparable-Interfacecall()-MethodenoSuchMethod überschreibenZusammenfassung – Eigene Collection14Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFactory-MusterObserver-MusterStrategy-Muster3Klasseneigenschaften
Instanz- vs. statische MemberFinal & Const FelderLate-VariablenStatische Methoden & FelderGetter und SetterZusammenfassung – Bankkonto-Manager6Vererbung
Grundlagen der VererbungDas super-SchlüsselwortMethoden überschreibenDie @override-AnnotationDas final-Klassen-SchlüsselwortKonstruktoren & VererbungRückblick – Mitarbeiter-Hierarchie9Polymorphismus
Polymorphismus-GrundlagenPolymorphismus über InterfacesTypprüfung zur LaufzeitDie is & as OperatorenDas covariant SchlüsselwortZusammenfassung – Payment Processor12Asynchrone OOP
Futures & async/awaitStreams GrundlagenStream ControllerAsynchrone KonstruktorenAsync in KlassenmethodenZusammenfassung – Data Fetcher15Entwurfsmuster Teil 2
Command-MusterAdapter-MusterDecorator-MusterTemplate-Method-MusterZustandsmusterComposite-MusterRepository-Muster