Menu
Coddy logo textTech

Decorator-Muster

Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 93 von 107.

Das Decorator-Muster ermöglicht es Ihnen, Objekten dynamisch neues Verhalten hinzuzufügen, indem Sie sie in andere Objekte einhüllen. Während der Adapter eine Schnittstelle ändert, um sie an eine andere anzupassen, behält der Decorator dieselbe Schnittstelle bei, erweitert jedoch die Funktionalität, indem er Wrapper um das ursprüngliche Objekt schichtet.

In Go implementieren wir dies, indem sowohl der Basistyp als auch die Dekoratoren dasselbe Interface implementieren:

type Notifier interface {
    Send(message string) string
}

type BasicNotifier struct{}

func (b BasicNotifier) Send(message string) string {
    return "Sending: " + message
}

Decorators umschließen einen anderen Notifier und fügen Verhalten hinzu, bevor oder nachdem sie an diesen delegieren:

type TimestampDecorator struct {
    wrapped Notifier
}

func (t TimestampDecorator) Send(message string) string {
    timestamped := "[2024-01-15] " + message
    return t.wrapped.Send(timestamped)
}

type UppercaseDecorator struct {
    wrapped Notifier
}

func (u UppercaseDecorator) Send(message string) string {
    return strings.ToUpper(u.wrapped.Send(message))
}

Die Stärke des Decorators liegt im Stapeln mehrerer Wrapper, um Verhaltensweisen zu kombinieren:

notifier := BasicNotifier{}
withTimestamp := TimestampDecorator{wrapped: notifier}
withBoth := UppercaseDecorator{wrapped: withTimestamp}

fmt.Println(notifier.Send("Hello"))
// Senden: Hello

fmt.Println(withBoth.Send("Hello"))
// SENDEN: [2024-01-15] HELLO

Das Decorator-Muster ist ideal, wenn Sie Objekten Verantwortlichkeiten hinzufügen müssen, ohne deren Code zu ändern, insbesondere wenn verschiedene Kombinationen von Funktionen benötigt werden. Es wird häufig für Logging-, Caching-, Authentifizierungs- und Komprimierungsschichten verwendet.

challenge icon

Aufgabe

Einfach

Lassen Sie uns ein Bestellsystem für ein Café mit dem Decorator-Muster erstellen! Sie werden ein Basis-Kaffeegetränk und Dekoratoren erstellen, die Extras wie Milch, Zucker und Schlagsahne hinzufügen. Jeder Dekorator umschließt den vorherigen und baut sowohl die Beschreibung als auch den Preis auf – ein perfektes Praxisbeispiel dafür, wie Dekoratoren Funktionalität schichten.

Sie werden Ihren Code auf drei Dateien verteilen:

  • beverage.go: Definieren Sie Ihr Kern-Interface und den Basis-Kaffeetyp.

    Erstellen Sie ein Beverage-Interface mit zwei Methoden:

    • Description() string — gibt an, was das Getränk ist
    • Cost() float64 — gibt den Preis zurück

    Implementieren Sie ein Coffee-Struct als Ihr Basisgetränk. Seine Beschreibung sollte Coffee sein und seine Kosten sollten 2.00 betragen.

  • decorators.go: Erstellen Sie Ihre Dekorator-Typen, die jedem Getränk Extras hinzufügen.

    Jeder Dekorator umschließt ein Beverage und implementiert dasselbe Interface. Erstellen Sie drei Dekoratoren:

    • MilkDecorator — fügt , Milk zur Beschreibung und 0.50 zu den Kosten hinzu
    • SugarDecorator — fügt , Sugar zur Beschreibung und 0.25 zu den Kosten hinzu
    • WhipDecorator — fügt , Whip zur Beschreibung und 0.75 zu den Kosten hinzu

    Jeder Dekorator sollte die Methoden des umschlossenen Getränks aufrufen und das Ergebnis erweitern.

  • main.go: Erstellen Sie maßgeschneiderte Getränke, indem Sie Dekoratoren stapeln.

    Lesen Sie eine Anzahl von Extras ein, die hinzugefügt werden sollen. Lesen Sie dann für jedes Extra dessen Typ (milk, sugar oder whip) ein und umschließen Sie Ihr aktuelles Getränk mit dem entsprechenden Dekorator.

    Geben Sie nach dem Anwenden aller Extras die Beschreibung und die Kosten des fertigen Getränks in separaten Zeilen aus. Formatieren Sie die Kosten als $X.XX mit genau zwei Dezimalstellen.

Die folgenden Eingaben werden bereitgestellt:

  • Zeile 1: Anzahl der hinzuzufügenden Extras
  • Folgende Zeilen: Ein Extra-Typ pro Zeile (milk, sugar oder whip)

Zum Beispiel, bei folgender Eingabe:

2
milk
sugar

Sollte Ihre Ausgabe wie folgt aussehen:

Coffee, Milk, Sugar
$2.75

Und bei folgender Eingabe:

3
whip
milk
milk

Sollte Ihre Ausgabe wie folgt aussehen:

Coffee, Whip, Milk, Milk
$3.75

Und bei folgender Eingabe:

0

Sollte Ihre Ausgabe wie folgt aussehen:

Coffee
$2.00

Beachten Sie, wie jeder Dekorator das vorherige Getränk umschließt und so sowohl die Beschreibung als auch die Kosten aufbaut. Sie können dasselbe Extra mehrmals hinzufügen (wie doppelte Milch), und die Reihenfolge der Dekoratoren bestimmt die Reihenfolge in der Beschreibung. Der Basis-Kaffee weiß nichts über die Extras – jeder Dekorator erweitert einfach das, was er umschließt!

Spickzettel

Das Decorator-Muster fügt Objekten dynamisch neues Verhalten hinzu, indem es sie in andere Objekte einpackt. Es behält die gleiche Schnittstelle bei, erweitert aber die Funktionalität durch Schichtung.

Sowohl der Basistyp als auch die Decorator implementieren dieselbe Schnittstelle:

type Notifier interface {
    Send(message string) string
}

type BasicNotifier struct{}

func (b BasicNotifier) Send(message string) string {
    return "Sending: " + message
}

Decorator umschließen ein anderes Objekt und fügen Verhalten vor oder nach der Delegierung hinzu:

type TimestampDecorator struct {
    wrapped Notifier
}

func (t TimestampDecorator) Send(message string) string {
    timestamped := "[2024-01-15] " + message
    return t.wrapped.Send(timestamped)
}

type UppercaseDecorator struct {
    wrapped Notifier
}

func (u UppercaseDecorator) Send(message string) string {
    return strings.ToUpper(u.wrapped.Send(message))
}

Stapeln Sie mehrere Decorator, um Verhaltensweisen zu kombinieren:

notifier := BasicNotifier{}
withTimestamp := TimestampDecorator{wrapped: notifier}
withBoth := UppercaseDecorator{wrapped: withTimestamp}

fmt.Println(notifier.Send("Hello"))
// Sending: Hello

fmt.Println(withBoth.Send("Hello"))
// SENDING: [2024-01-15] HELLO

Verwenden Sie den Decorator, um Verantwortlichkeiten hinzuzufügen, ohne den Objektcode zu ändern, insbesondere für verschiedene Merkmalskombinationen. Häufige Anwendungen: Logging, Caching, Authentifizierung, Komprimierung.

Probier es selbst

package main

import (
	"fmt"
)

func main() {
	// Lies die Anzahl der Extras
	var count int
	fmt.Scanln(&count)

	// Beginne mit einem Basis-Kaffee
	var drink Beverage = &Coffee{}

	// Lies jedes Extra und umhülle das Getränk mit dem entsprechenden Decorator
	for i := 0; i < count; i++ {
		var extra string
		fmt.Scanln(&extra)

		// TODO: Basierend auf dem Typ des Extras, umhülle drink mit dem entsprechenden Decorator
		// Verwende MilkDecorator für "milk"
		// Verwende SugarDecorator für "sugar"
		// Verwende WhipDecorator für "whip"
	}

	// TODO: Gib die endgültige Beschreibung und die Kosten aus
	// Formatiere die Kosten als $X.XX mit genau zwei Dezimalstellen
}
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