Menu
Coddy logo textTech

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 bestehender Typ mit einer neuen Schnittstelle umhüllt wird. Während Command Aktionen kapselt, übersetzt Adapter eine Schnittstelle in eine andere, die Clients erwarten.

Stellen Sie sich vor, Sie haben einen veralteten Drucker, der eine andere Methodensignatur verwendet, als Ihre Anwendung erwartet:

// Ziel-Interface, das Ihre Anwendung verwendet
type Printer interface {
    Print(text string) string
}

// Veralteter 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 kapselt den veralteten 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 arbeitet der Legacy-Drucker nahtlos mit Code zusammen, der das Printer-Interface 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 von unschätzbarem Wert bei der Integration von Bibliotheken von Drittanbietern, bei der Arbeit mit Legacy-Code oder wenn Sie vorhandene Klassen verwenden müssen, die nicht Ihren Schnittstellenanforderungen entsprechen. Es fungiert als Brücke, ohne den ursprünglichen Code zu ändern.

challenge icon

Aufgabe

Einfach

Lassen Sie uns ein Media-Player-System mit dem Adapter-Muster bauen! Sie haben einen modernen Audioplayer, der eine einheitliche Schnittstelle erwartet, aber Sie müssen veraltete Audiogeräte integrieren, die völlig andere Methodensignaturen verwenden. Ihr Adapter wird diese Lücke schließen und es der alten Hardware ermöglichen, nahtlos mit dem neuen System zusammenzuarbeiten.

Sie werden Ihren Code über drei Dateien organisieren:

  • player.go: Definieren Sie Ihr Ziel-Interface und ein veraltetes Audiogerät.

    Erstellen Sie ein AudioPlayer-Interface, das Ihr modernes System erwartet, mit einer Methode Play(track string) string, die einen Tracknamen entgegennimmt und eine Wiedergabenachricht zurückgibt.

    Definieren Sie außerdem ein veraltetes Gerät, das nicht zu diesem Interface passt:

    • VinylPlayer-Struktur mit einem SpeedRPM-Feld (int) — sie hat eine Methode DropNeedle(record string, side string) string, die Playing [record] ([side] side) at [speed]rpm zurückgibt.

    Das Interface des VinylPlayer ist inkompatibel mit AudioPlayer — es erfordert zwei Parameter und verwendet einen völlig anderen Methodennamen.

  • adapter.go: Erstellen Sie Ihren Adapter, der das veraltete Gerät kompatibel macht.

    Erstellen Sie eine VinylAdapter-Struktur, die einen VinylPlayer kapselt und das AudioPlayer-Interface implementiert. Wenn Play aufgerufen wird, sollte der Adapter die DropNeedle-Methode des Vinyl-Players aufrufen und dabei standardmäßig "A" für den Parameter side verwenden.

    Erstellen Sie zum Vergleich auch einen modernen Player:

    • DigitalPlayer-Struktur — ihre Play-Methode gibt Streaming: [track] zurück.
  • main.go: Demonstrieren Sie, wie beide Player über dasselbe Interface funktionieren.

    Erstellen Sie eine Funktion PlayMusic(player AudioPlayer, track string) string, die das Interface zum Abspielen von Musik verwendet — diese Funktion sollte nicht wissen oder sich darum kümmern, ob sie einen digitalen Player oder einen adaptierten Vinyl-Player verwendet.

    Lesen Sie einen Player-Typ ("digital" oder "vinyl") ein, und falls es vinyl ist, lesen Sie auch die RPM-Geschwindigkeit ein. Lesen Sie dann den Tracknamen ein. Erstellen Sie den entsprechenden Player (unter Verwendung des Adapters für Vinyl), spielen Sie den Track über Ihre PlayMusic-Funktion ab und geben Sie das Ergebnis aus.

Die folgenden Eingaben werden bereitgestellt:

  • Zeile 1: Player-Typ ("digital" oder "vinyl")
  • Zeile 2: RPM-Geschwindigkeit (nur bei Vinyl)
  • Letzte Zeile: Name des abzuspielenden Tracks

Zum Beispiel, gegeben:

digital
Bohemian Rhapsody

Ihre Ausgabe sollte sein:

Streaming: Bohemian Rhapsody

Und gegeben:

vinyl
33
Abbey Road

Ihre Ausgabe sollte sein:

Playing Abbey Road (A side) at 33rpm

Und gegeben:

vinyl
45
Blue Monday

Ihre Ausgabe sollte sein:

Playing Blue Monday (A side) at 45rpm

Beachten Sie, wie die Funktion PlayMusic mit beiden Playern identisch funktioniert — sie hat keine Ahnung, dass der Vinyl-Player eigentlich von einem völlig anderen Interface adaptiert wird. Der Adapter übersetzt die modernen Interface-Aufrufe hinter den Kulissen in das veraltete Format!

Spickzettel

Das Adapter-Muster ermöglicht es inkompatiblen Schnittstellen zusammenzuarbeiten, indem ein bestehender Typ mit einer neuen Schnittstelle umhüllt wird.

Definieren Sie eine Ziel-Schnittstelle (Target Interface), die Ihre Anwendung erwartet:

type Printer interface {
    Print(text string) string
}

Ein veralteter Typ (Legacy Type) mit einer inkompatiblen Schnittstelle:

type OldPrinter struct{}

func (o OldPrinter) PrintDocument(doc string, copies int) string {
    return fmt.Sprintf("Old printer: %s (x%d)", doc, copies)
}

Erstellen Sie einen Adapter, der den veralteten Typ umhüllt und die Ziel-Schnittstelle implementiert:

type OldPrinterAdapter struct {
    oldPrinter OldPrinter
}

func (a OldPrinterAdapter) Print(text string) string {
    return a.oldPrinter.PrintDocument(text, 1)
}

Verwenden Sie den Adapter, damit der veraltete Typ mit Code funktioniert, der die Ziel-Schnittstelle 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 nützlich für die Integration von Drittanbieter-Bibliotheken, die Arbeit mit Legacy-Code oder die Anpassung bestehender Klassen an erforderliche Schnittstellen, ohne den ursprünglichen Code zu ändern.

Probier es selbst

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

// PlayMusic verwendet das AudioPlayer-Interface, um Musik abzuspielen
// Diese Funktion arbeitet mit jeder AudioPlayer-Implementierung
// TODO: Implementiere diese Funktion, um player.Play(track) aufzurufen und das Ergebnis zurückzugeben
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: Hülle ihn in einen VinylAdapter ein
		// TODO: Der Variable player zuweisen
		_ = 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: Der Variable player zuweisen
	}
	
	// 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
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung