Adapter-Muster
Teil des Abschnitts Objektorientierte Programmierung der C#-Journey von Coddy — Lektion 57 von 70.
Das Adapter-Muster ist ein Strukturmuster, das es inkompatiblen Schnittstellen ermöglicht, zusammenzuarbeiten. Es fungiert als Brücke zwischen zwei Klassen und wandelt die Schnittstelle einer Klasse in eine Schnittstelle um, die Clients erwarten. Stellen Sie es sich wie einen Reiseadapter vor, mit dem Sie ein europäisches Gerät an eine amerikanische Steckdose anschließen können.
Dieses Muster ist besonders nützlich, wenn Sie eine bestehende Klasse verwenden möchten, deren Schnittstelle jedoch nicht den Anforderungen Ihres Codes entspricht. Anstatt die ursprüngliche Klasse zu ändern (die Sie möglicherweise nicht kontrollieren können), erstellen Sie einen Adapter, der sie umschließt:
// Bestehende Klasse mit inkompatibler Schnittstelle
public class OldPrinter
{
public void PrintMessage(string msg) => Console.WriteLine($"Old: {msg}");
}
// Schnittstelle, die Ihr Code erwartet
public interface IPrinter
{
void Print(string text);
}
// Adapter überbrückt die Lücke
public class PrinterAdapter : IPrinter
{
private OldPrinter _oldPrinter;
public PrinterAdapter(OldPrinter oldPrinter)
{
_oldPrinter = oldPrinter;
}
public void Print(string text)
{
_oldPrinter.PrintMessage(text); // Delegiert an die alte Schnittstelle
}
}Jetzt kann Ihr Code über die erwartete Schnittstelle mit dem alten Drucker arbeiten:
OldPrinter legacy = new OldPrinter();
IPrinter printer = new PrinterAdapter(legacy);
printer.Print("Hello"); // Alt: HelloDas Adapter-Muster ist von unschätzbarem Wert, wenn Sie Bibliotheken von Drittanbietern oder Legacy-Code in Ihre Anwendung integrieren. Der Client-Code arbeitet mit der IPrinter-Schnittstelle und ist sich völlig unbewusst darüber, dass eine alte, inkompatible Klasse im Hintergrund die eigentliche Arbeit verrichtet.
Aufgabe
EinfachLassen Sie uns ein Mediaplayer-System unter Verwendung des Adapter-Musters erstellen. Sie verfügen über eine bestehende Audio-Bibliothek, die MP3-Dateien abspielt, aber Ihre Anwendung benötigt eine einheitliche Schnittstelle, die verschiedene Medientypen verarbeiten kann. Sie werden einen Adapter erstellen, um diese Lücke zu schließen, sodass Ihr moderner Mediaplayer nahtlos mit der veralteten Audio-Bibliothek zusammenarbeiten kann.
Sie werden Ihren Code über drei Dateien organisieren:
LegacyAudio.cs: Dies repräsentiert eine bestehende Bibliothek, die Sie nicht ändern können. Erstellen Sie eine KlasseLegacyAudioPlayerim NamespaceMediaSystemmit einer MethodePlayMp3(string filename), die den String"Playing MP3: {filename}"zurückgibt. Dies ist Ihr "Adaptee" – die Klasse mit der inkompatiblen Schnittstelle.MediaAdapter.cs: Erstellen Sie das Interface und den Adapter im selben Namespace. Definieren Sie einIMediaPlayer-Interface mit einer MethodePlay(string filename), die einen String zurückgibt. Erstellen Sie dann eine KlasseMediaPlayerAdapter, dieIMediaPlayerimplementiert. Ihr Adapter sollte eineLegacyAudioPlayer-Instanz kapseln (die über den Konstruktor übergeben wird) und denPlay-Aufruf in diePlayMp3-Methode des Legacy-Players übersetzen.Program.cs: Führen Sie alles zusammen, indem Sie den Legacy-Player erstellen, ihn in Ihren Adapter einhüllen und den Adapter über dasIMediaPlayer-Interface verwenden. Dies demonstriert, wie Client-Code mit der modernen Schnittstelle arbeiten kann, während der Adapter im Hintergrund die Kommunikation mit dem Legacy-System übernimmt.
Sie erhalten eine Eingabe:
- Den Dateinamen, der abgespielt werden soll (z. B.
song.mp3)
Erstellen Sie einen LegacyAudioPlayer, hüllen Sie ihn in einen MediaPlayerAdapter ein, rufen Sie dann Play auf dem Adapter über das IMediaPlayer-Interface auf und geben Sie das Ergebnis aus.
Wenn die Eingabe beispielsweise favorite_track.mp3 ist, sollte die Ausgabe wie folgt lauten:
Playing MP3: favorite_track.mp3Das Schöne an diesem Muster ist, dass Ihr Hauptcode nur mit IMediaPlayer interagiert – er hat keine Ahnung, dass eine veraltete Audio-Bibliothek die eigentliche Arbeit erledigt. Wenn Sie später eine andere Legacy-Bibliothek unterstützen müssen, erstellen Sie einfach einen neuen Adapter, ohne den Client-Code zu ändern!
Spickzettel
Das Adapter-Muster ist ein Strukturmuster, das es inkompatiblen Schnittstellen ermöglicht, zusammenzuarbeiten, indem es als Brücke zwischen zwei Klassen fungiert.
Das Muster wandelt die Schnittstelle einer Klasse in eine Schnittstelle um, die Clients erwarten. Dies ist nützlich, wenn Sie eine bestehende Klasse verwenden möchten, deren Schnittstelle jedoch nicht den Anforderungen Ihres Codes entspricht.
Grundstruktur:
// Bestehende Klasse mit inkompatibler Schnittstelle (Adaptee)
public class OldPrinter
{
public void PrintMessage(string msg) => Console.WriteLine($"Old: {msg}");
}
// Ziel-Schnittstelle, die Ihr Code erwartet
public interface IPrinter
{
void Print(string text);
}
// Adapter überbrückt die Lücke
public class PrinterAdapter : IPrinter
{
private OldPrinter _oldPrinter;
public PrinterAdapter(OldPrinter oldPrinter)
{
_oldPrinter = oldPrinter;
}
public void Print(string text)
{
_oldPrinter.PrintMessage(text); // Delegiert an die alte Schnittstelle
}
}Verwendung:
OldPrinter legacy = new OldPrinter();
IPrinter printer = new PrinterAdapter(legacy);
printer.Print("Hello"); // Old: HelloDer Client-Code arbeitet mit der Ziel-Schnittstelle, ohne zu wissen, dass eine inkompatible Klasse im Hintergrund die eigentliche Arbeit verrichtet. Dieses Muster ist besonders wertvoll bei der Integration von Drittanbieter-Bibliotheken oder Legacy-Code.
Probier es selbst
using System;
using MediaSystem;
class Program
{
public static void Main(string[] args)
{
// Lies die Dateinamen-Eingabe ein
string filename = Console.ReadLine();
// TODO: Erstelle eine LegacyAudioPlayer-Instanz
// TODO: Umschließe sie mit einem MediaPlayerAdapter
// TODO: Verwende den Adapter über das IMediaPlayer-Interface
// und gib das Ergebnis des Aufrufs von Play mit dem Dateinamen 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 DateienNamespaces & DirektivenEinführung in Klassen & ObjekteDas 'this'-SchlüsselwortMethoden und ParameterFelder vs. EigenschaftenKonstruktorenObjekt-InitialisiererZusammenfassung - Einfacher Taschenrechner4Vererbung
Grundlagen der Vererbung (:) SyntaxDas 'base'-SchlüsselwortVirtual & Override SchlüsselwörterVersiegelte KlassenDie 'object'-BasisklasseZusammenfassung – Mitarbeiter-Hierarchie7Fortgeschrittene Funktionen
OperatorüberladungIndexer (this[])ToString() überschreibenErweiterungsmethodenZusammenfassung - Benutzerdefinierte Liste2Eigenschaften & Statische Member
Automatisch implementierte EigenschaftenRead-Only- & Write-Only-EigenschaftenStatische Felder & MethodenStatische KlassenExpression-Bodied Member5Polymorphismus & Interfaces
Compile- vs. Runtime-PolymorphismusInterface vs. abstrakte KlasseMehrfache InterfacesExplizite InterfacesUpcasting & DowncastingRecap – Shape Calculator8Fortgeschrittene OOP-Konzepte
Komposition statt VererbungGenerics (Klassen & Methoden)Delegates und EventsAttribute und ReflectionIDisposable & using-AnweisungDependency Injection Grundlagen11Entwurfsmuster Teil 2
Command-MusterAdapter-MusterDecorator-MusterTemplate-Method-MusterZustandsmusterKompositum-Muster3Klassenarchitektur
Instanz- vs. statische Daten'readonly' & 'const' SchlüsselwörterBacking FieldsZusammenfassung - Bankkonto-Manager6Kapselung
ZugriffsmodifikatorenProperties zur KapselungImplementierung von Data HidingImmutability-MusterRückblick – Studierendendaten