Menu
Coddy logo textTech

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 ausgeschaltet

Command 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.

challenge icon

Aufgabe

Einfach

Lass 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, und Undo() string, um sie rückgängig zu machen.

    Implementiere zwei Befehlstypen, die mit einem *TextEditor arbeiten:

    • InsertCommand: Enthält den Editorzeiger und den einzufügenden Text. Execute hängt den Text an den Inhalt des Editors an und gibt Inserted: [text] zurück. Undo entfernt diesen Text vom Ende und gibt Undone 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 gibt Deleted: [removed text] zurück. Undo stellt sie wieder her und gibt Undone delete: [restored text] zurück.
  • editor.go: Erstelle deinen Texteditor (den Receiver) und den Invoker, der den Befehlsverlauf verwaltet.

    Erstelle eine TextEditor-Struktur mit einem Content-Feld (string) und den Methoden Append(text string) sowie DeleteLast(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, gib Nothing to undo zurü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, delete oder undo). 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 als Final: [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
undo

Deine Ausgabe sollte folgendermaßen aussehen:

Inserted: Hello
Inserted:  World
Deleted: rld
Undone delete: rld
Undone insert:  World
Final: Hello

Und 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
Test

Deine Ausgabe sollte folgendermaßen aussehen:

Nothing to undo
Inserted: Test
Final: Test

Beachte, 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)
}
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

Übe selbstständig: Online-Go-Compiler