Menu
CoddyTech

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 ganze Aktionen mitsamt ihren Parametern.

In Go definieren wir ein Command-Interface mit einer Execute-Methode und erstellen dann konkrete Befehle, die alle Informationen enthalten, die zum Ausführen einer Aktion erforderlich sind:

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, wenn du Vorgänge speichern, verzögern oder wiederholen musst.

challenge icon

Aufgabe

Einfach

Wir erstellen einen Texteditor mit Rückgängig-Funktion mithilfe des Command-Musters! Du erstellst Befehle, die Textoperationen kapseln und es dem Editor ermöglichen, Aktionen auszuführen und rückgängig zu machen: ein klassischer Anwendungsfall, der zeigt, warum Befehle so leistungsstark sind.

Du organisierst deinen Code in drei Dateien:

  • command.go: Definiere dein Befehlsinterface und konkrete Textbearbeitungsbefehle.

    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 auf einem *TextEditor arbeiten:

    • InsertCommand: Speichert den Zeiger auf den Editor 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: Speichert den Zeiger auf den Editor und die Anzahl der Zeichen, die vom 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 Empfänger) und den Aufrufer, 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 eine Liste der ausgeführten Befehle als Verlauf speichert. Füge diese 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: Führe dein Befehlssystem anhand einer Reihe von Operationen vor.

    Lies die Anzahl der Operationen ein. Lies für jede Operation den Operationstyp (insert, delete oder undo) ein. Lies bei insert zusätzlich den einzufügenden Text ein. Lies bei delete die Anzahl der Zeichen ein. Führe jede Operation über deinen Aufrufer 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, dann 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 so 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 so aussehen:

Inserted: Go
Inserted: Lang
Undone insert: Lang
Inserted: !
Final: Go!

Und bei folgender Eingabe:

2
undo
insert
Test

Deine Ausgabe sollte so aussehen:

Nothing to undo
Inserted: Test
Final: Test

Beachte, dass jeder Befehl alles kapselt, was nötig ist, um seine Aktion sowohl auszuführen als auch rückgängig zu machen. Der Aufrufer weiß nicht, was die Befehle tun. Er führt sie lediglich aus und verwaltet den Verlauf, damit Aktionen rückgängig gemacht werden können!

Probier es selbst

package main

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

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// Anzahl der Operationen einlesen
	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 einlesen
		opLine, _ := reader.ReadString('\n')
		opType := strings.TrimSpace(opLine)
		
		var result string
		
		switch opType {
		case "insert":
			// Einzufügenden Text einlesen
			textLine, _ := reader.ReadString('\n')
			text := strings.TrimSuffix(textLine, "\n")
			
			// TODO: Einen InsertCommand erstellen und über den Invoker ausführen
			_ = text
			_ = editor
			
		case "delete":
			// Anzahl der zu löschenden Zeichen einlesen
			countLine, _ := reader.ReadString('\n')
			count, _ := strconv.Atoi(strings.TrimSpace(countLine))
			
			// TODO: Einen DeleteCommand erstellen und über den Invoker ausführen
			_ = count
			
		case "undo":
			// TODO: UndoLast auf dem Invoker aufrufen
			
		}
		
		fmt.Println(result)
	}
	
	// Endgültigen 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