Command-Muster
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy. Lektion 91 von 107.
Das Command-Muster kapselt eine Anfrage als Objekt und ermöglicht es dir, Operationen zu parametrisieren, sie in eine Warteschlange einzureihen oder eine Rückgängig-Funktion zu unterstützen. Während Strategy Algorithmen kapselt, kapselt Command vollständige Aktionen zusammen mit ihren Parametern.
In Go definieren wir ein Command-Interface mit einer Execute-Methode und erstellen anschließend konkrete Befehle, die alle zum Ausführen einer Aktion erforderlichen Informationen enthalten:
type Command interface {
Execute() string
}
type Light struct {
IsOn bool
}
type TurnOnCommand struct {
light *Light
}
func (c *TurnOnCommand) Execute() string {
c.light.IsOn = true
return "Light turned on"
}
type TurnOffCommand struct {
light *Light
}
func (c *TurnOffCommand) Execute() string {
c.light.IsOn = false
return "Light turned off"
}Ein Aufrufer speichert Befehle und führt sie aus, ohne zu wissen, was sie bewirken:
type RemoteControl struct {
command Command
}
func (r *RemoteControl) SetCommand(c Command) {
r.command = c
}
func (r *RemoteControl) PressButton() string {
return r.command.Execute()
}Der Aufrufer ist vollständig vom Empfänger (dem Licht) entkoppelt:
light := &Light{}
remote := &RemoteControl{}
remote.SetCommand(&TurnOnCommand{light: light})
fmt.Println(remote.PressButton()) // Licht eingeschaltet
remote.SetCommand(&TurnOffCommand{light: light})
fmt.Println(remote.PressButton()) // Licht ausgeschaltetCommand eignet sich ideal für die Implementierung von Rückgängig-/Wiederholen-Systemen, Aufgabenwarteschlangen oder Makroaufzeichnungen, bei denen du Vorgänge speichern, verzögern oder erneut ausführen musst.
Aufgabe
EinfachLass uns mit dem Command-Muster einen Texteditor mit Rückgängig-Funktion erstellen! Du wirst Befehle erstellen, die Textoperationen kapseln, sodass der Editor Aktionen ausführen und rückgängig machen kann: ein klassischer Anwendungsfall, der zeigt, warum Befehle so mächtig sind.
Du wirst deinen Code auf drei Dateien verteilen:
command.go: Definiere dein Command-Interface und konkrete Befehle zur Textbearbeitung.Erstelle ein
Command-Interface mit zwei Methoden:Execute() string, um die Aktion auszuführen, undUndo() string, um sie rückgängig zu machen.Implementiere zwei Befehlstypen, die mit einem
*TextEditorarbeiten:InsertCommand: Enthält den Editorzeiger und den einzufügenden Text. Execute hängt den Text an den Inhalt des Editors an und gibtInserted: [text]zurück. Undo entfernt diesen Text vom Ende und gibtUndone insert: [text]zurück.DeleteCommand: Enthält den Editorzeiger und die Anzahl der Zeichen, die am Ende gelöscht werden sollen. Execute entfernt diese Zeichen (und speichert sie für Undo) und gibtDeleted: [removed text]zurück. Undo stellt sie wieder her und gibtUndone delete: [restored text]zurück.
editor.go: Erstelle deinen Texteditor (den Receiver) und den Invoker, der den Befehlsverlauf verwaltet.Erstelle eine
TextEditor-Struktur mit einemContent-Feld (string) und den MethodenAppend(text string)sowieDeleteLast(count int) string(gibt den gelöschten Text zurück).Erstelle eine
EditorInvoker-Struktur, die ein Slice der ausgeführten Befehle als Verlauf enthält. Füge die folgenden Methoden hinzu:ExecuteCommand(cmd Command) string: Führt den Befehl aus, fügt ihn dem Verlauf hinzu und gibt das Ergebnis zurück.UndoLast() string: Entfernt den letzten Befehl aus dem Verlauf, ruft dessen Undo auf und gibt das Ergebnis zurück. Wenn der Verlauf leer ist, gibNothing to undozurück.
main.go: Demonstriere dein Befehlssystem mit einer Reihe von Operationen.Lies die Anzahl der Operationen ein. Lies für jede Operation den Operationstyp (
insert,deleteoderundo). Lies bei insert zusätzlich den einzufügenden Text ein. Lies bei delete die Anzahl der Zeichen ein. Führe jede Operation über deinen Invoker aus und gib das Ergebnis aus. Gib nach allen Operationen den endgültigen Inhalt des Editors alsFinal: [content]aus.
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Anzahl der Operationen
- Für jede Operation: Operationstyp, danach bei Bedarf zusätzliche Daten (Text für insert, Anzahl für delete)
Zum Beispiel bei folgender Eingabe:
5
insert
Hello
insert
World
delete
3
undo
undoDeine Ausgabe sollte folgendermaßen aussehen:
Inserted: Hello
Inserted: World
Deleted: rld
Undone delete: rld
Undone insert: World
Final: HelloUnd bei folgender Eingabe:
4
insert
Go
insert
Lang
undo
insert
!Deine Ausgabe sollte folgendermaßen aussehen:
Inserted: Go
Inserted: Lang
Undone insert: Lang
Inserted: !
Final: Go!Und bei folgender Eingabe:
2
undo
insert
TestDeine Ausgabe sollte folgendermaßen aussehen:
Nothing to undo
Inserted: Test
Final: TestBeachte, wie jeder Befehl alles kapselt, was benötigt wird, um seine Aktion sowohl auszuführen als auch rückgängig zu machen. Der Invoker weiß nicht, was die Befehle tun. Er führt sie einfach aus und verwaltet den Verlauf, um das Rückgängigmachen zu unterstützen!
Probier es selbst
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Anzahl der Operationen lesen
line, _ := reader.ReadString('\n')
numOps, _ := strconv.Atoi(strings.TrimSpace(line))
// Editor und Invoker erstellen
editor := &TextEditor{}
invoker := &EditorInvoker{}
for i := 0; i < numOps; i++ {
// Operationstyp lesen
opLine, _ := reader.ReadString('\n')
opType := strings.TrimSpace(opLine)
var result string
switch opType {
case "insert":
// Einzufügenden Text lesen
textLine, _ := reader.ReadString('\n')
text := strings.TrimSuffix(textLine, "\n")
// TODO: Erstelle ein InsertCommand und führe es über den Invoker aus
_ = text
_ = editor
case "delete":
// Anzahl der zu löschenden Zeichen lesen
countLine, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countLine))
// TODO: Erstelle ein DeleteCommand und führe es über den Invoker aus
_ = count
case "undo":
// TODO: Rufe UndoLast auf dem Invoker auf
}
fmt.Println(result)
}
// Finalen Inhalt ausgeben
fmt.Printf("Final: %s\n", editor.Content)
}
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