Adapter-Muster
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy. Lektion 92 von 107.
Das Adapter-Muster ermöglicht es inkompatiblen Schnittstellen, zusammenzuarbeiten, indem ein vorhandener Typ mit einer neuen Schnittstelle umschlossen wird. Während Command Aktionen kapselt, übersetzt Adapter eine Schnittstelle in eine andere, die Clients erwarten.
Stell dir vor, du hast einen alten Drucker, der eine andere Methodensignatur verwendet als von deiner Anwendung erwartet:
// Ziel-Interface, das Ihre Anwendung verwendet
type Printer interface {
Print(text string) string
}
// Legacy-Drucker mit inkompatiblem Interface
type OldPrinter struct{}
func (o OldPrinter) PrintDocument(doc string, copies int) string {
return fmt.Sprintf("Old printer: %s (x%d)", doc, copies)
}Der Adapter umschließt den Legacy-Typ und implementiert die erwartete Schnittstelle:
type OldPrinterAdapter struct {
oldPrinter OldPrinter
}
func (a OldPrinterAdapter) Print(text string) string {
return a.oldPrinter.PrintDocument(text, 1)
}Jetzt funktioniert der alte Drucker nahtlos mit Code, der die Schnittstelle Printer erwartet:
func PrintMessage(p Printer, msg string) string {
return p.Print(msg)
}
adapter := OldPrinterAdapter{oldPrinter: OldPrinter{}}
result := PrintMessage(adapter, "Hello") // "Old printer: Hello (x1)"Das Adapter-Muster ist bei der Integration von Bibliotheken von Drittanbietern, bei der Arbeit mit Legacy-Code oder wenn du vorhandene Klassen verwenden musst, die nicht deinen Schnittstellenanforderungen entsprechen, von unschätzbarem Wert. Es fungiert als Brücke, ohne den ursprünglichen Code zu ändern.
Aufgabe
EinfachErstellen wir ein Mediaplayer-System mit dem Adapter-Pattern! Du hast einen modernen Audioplayer, der eine einheitliche Schnittstelle erwartet, musst aber ältere Audiogeräte integrieren, die völlig andere Methodensignaturen verwenden. Dein Adapter überbrückt diese Lücke und ermöglicht es der alten Hardware, nahtlos mit dem neuen System zu funktionieren.
Du organisierst deinen Code über drei Dateien:
player.go: Definiere deine Zielschnittstelle und ein älteres Audiogerät.Erstelle eine
AudioPlayer-Schnittstelle, die dein modernes System erwartet, mit einer MethodePlay(track string) string, die einen Titelnamen entgegennimmt und eine Wiedergabenachricht zurückgibt.Definiere außerdem ein älteres Gerät, das nicht zu dieser Schnittstelle passt:
VinylPlayer-Struktur mit einemSpeedRPM-Feld (int). Sie verfügt über eine MethodeDropNeedle(record string, side string) string, diePlaying [record] ([side] side) at [speed]rpmzurückgibt.
Die Schnittstelle von VinylPlayer ist nicht mit AudioPlayer kompatibel. Sie benötigt zwei Parameter und verwendet einen völlig anderen Methodennamen.
adapter.go: Erstelle deinen Adapter, der das ältere Gerät kompatibel macht.Erstelle eine
VinylAdapter-Struktur, die einenVinylPlayerumschließt und dieAudioPlayer-Schnittstelle implementiert. WennPlayaufgerufen wird, soll der Adapter die MethodeDropNeedledes Vinylplayers aufrufen und für den Seitenparameter standardmäßigAverwenden.Erstelle außerdem zum Vergleich einen modernen Player:
DigitalPlayer-Struktur: IhrePlay-Methode gibtStreaming: [track]zurück
main.go: Zeige, wie beide Player über dieselbe Schnittstelle funktionieren.Erstelle eine Funktion
PlayMusic(player AudioPlayer, track string) string, die die Schnittstelle zum Abspielen von Musik verwendet. Diese Funktion sollte nicht wissen oder sich darum kümmern müssen, ob sie einen digitalen Player oder einen adaptierten Vinylplayer verwendet.Lies einen Playertyp (
digitalodervinyl) ein und lies bei vinyl zusätzlich die RPM-Geschwindigkeit ein. Lies anschließend den Titelnamen ein. Erstelle den passenden Player (für vinyl mithilfe des Adapters), spiele den Titel über deine FunktionPlayMusicab und gib das Ergebnis aus.
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Playertyp (
digitalodervinyl) - Zeile 2: RPM-Geschwindigkeit (nur bei vinyl)
- Letzte Zeile: Abzuspielender Titel
Zum Beispiel bei folgender Eingabe:
digital
Bohemian RhapsodyDeine Ausgabe sollte lauten:
Streaming: Bohemian RhapsodyUnd bei folgender Eingabe:
vinyl
33
Abbey RoadDeine Ausgabe sollte lauten:
Playing Abbey Road (A side) at 33rpmUnd bei folgender Eingabe:
vinyl
45
Blue MondayDeine Ausgabe sollte lauten:
Playing Blue Monday (A side) at 45rpmBeachte, wie die Funktion PlayMusic mit beiden Playern identisch funktioniert. Sie weiß nicht, dass der Vinylplayer tatsächlich von einer völlig anderen Schnittstelle adaptiert wird. Der Adapter übersetzt die Aufrufe der modernen Schnittstelle im Hintergrund in das Format des älteren Systems!
Probier es selbst
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
// PlayMusic verwendet die AudioPlayer-Schnittstelle, um Musik abzuspielen
// Diese Funktion funktioniert mit jeder AudioPlayer-Implementierung
// TODO: Implementiere diese Funktion so, dass sie player.Play(track) aufruft und das Ergebnis zurückgibt
func PlayMusic(player AudioPlayer, track string) string {
// TODO: Implementiere diese Funktion
return ""
}
func main() {
reader := bufio.NewReader(os.Stdin)
// Player-Typ einlesen
playerType, _ := reader.ReadString('\n')
playerType = strings.TrimSpace(playerType)
var player AudioPlayer
var track string
if playerType == "vinyl" {
// RPM-Geschwindigkeit für Vinyl einlesen
rpmStr, _ := reader.ReadString('\n')
rpmStr = strings.TrimSpace(rpmStr)
rpm, _ := strconv.Atoi(rpmStr)
// Track-Namen einlesen
track, _ = reader.ReadString('\n')
track = strings.TrimSpace(track)
// TODO: Erstelle einen VinylPlayer mit der angegebenen RPM
// TODO: Verpacke ihn in einen VinylAdapter
// TODO: Weise der player-Variable zu
_ = rpm // Entferne diese Zeile, wenn du rpm verwendest
} else {
// Track-Namen einlesen
track, _ = reader.ReadString('\n')
track = strings.TrimSpace(track)
// TODO: Erstelle einen DigitalPlayer
// TODO: Weise der player-Variable zu
}
// TODO: Rufe PlayMusic auf und gib das Ergebnis aus
_ = player // Entferne diese Zeile, wenn du player verwendest
_ = track // Entferne diese Zeile, wenn du track verwendest
}
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 in Go
Externe DateienGo-Workspace & ModulePackages & ImportsExportierte vs. nicht exportierte NamenEinführung in OOP mit GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktorfunktionenRückblick – Einfacher Taschenrechner4Schnittstellen
Einführung in SchnittstellenImplizite ImplementierungSchnittstelle als VertragLeere Schnittstelle (any)TypzusicherungTypwechselZusammensetzung von SchnittstellenStringer- und Error-SchnittstellenRückblick – Formenrechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf PaketebeneGetter- und Setter-MethodenInformationsverbergung in GoRückblick – Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterTypbeschränkungenGenerische StrukturenWorkaround für generische MethodenZusammenfassung – Generische Sammlung2Typen & Structs im Detail
Grundlegende & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct-TagsAnonyme StructsVerschachtelte StructsNullwerte & StandardwerteRückblick – Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen der Struct-EinbettungMethoden-PromotionMehrere Structs einbettenEinbettung vs. AggregationVerbergen eingebetteter MethodenRückblick – Mitarbeiterhierarchie8Fehlerbehandlung & OOP
Das Error-InterfaceBenutzerdefinierte FehlertypenError-Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverRückblick – Dateiparser11Standardbibliothek & OOP
io.Reader & io.Writersort.Interfacefmt.Stringer-Interfaceencoding/json mit Structshttp.Handler-InterfaceRückblick – REST-API-Modelle14Entwurfsmuster Teil 2
Command-MusterAdapter-MusterDecorator-MusterTemplate-Methoden-MusterState-MusterComposite-MusterMiddleware als Decorator3Zeiger & Speicher
Grundlagen von Zeigern in GoZeiger auf StructsÜbergabe per Wert vs. ReferenzDie Funktion new()Garbage Collection in GoRückblick – Verkettete Liste erstellen6Polymorphismus in Go
Polymorphismus über InterfacesDuck-Typing in GoRegeln zur Interface-ErfüllungPolymorphe SammlungenDependency InjectionZusammenfassung – Zahlungsprozessor9Konkurrenz & OOP
Grundlagen der GoroutinesChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignRückblick – Worker PoolÜbe selbstständig: Online-Go-Compiler