Menu
CoddyTech

Паттерн Команда

Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy. Урок 91 из 107.

Паттерн Command инкапсулирует запрос в объект, позволяя параметризовать операции, ставить их в очередь или поддерживать отмену действий. Если Strategy инкапсулирует алгоритмы, то Command инкапсулирует целые действия вместе с их параметрами.

В Go мы определяем интерфейс Command с методом Execute, а затем создаём конкретные команды, содержащие всю информацию, необходимую для выполнения действия:

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"
}

Инициатор хранит и выполняет команды, не зная, что они делают:

type RemoteControl struct {
    command Command
}

func (r *RemoteControl) SetCommand(c Command) {
    r.command = c
}

func (r *RemoteControl) PressButton() string {
    return r.command.Execute()
}

Инициатор полностью отделён от получателя (светильника):

light := &Light{}
remote := &RemoteControl{}

remote.SetCommand(&TurnOnCommand{light: light})
fmt.Println(remote.PressButton())  // Свет включён

remote.SetCommand(&TurnOffCommand{light: light})
fmt.Println(remote.PressButton())  // Свет выключен

Command идеально подходит для реализации систем отмены и повтора, очередей задач или записи макросов, когда нужно сохранять, откладывать или повторно выполнять операции.

challenge icon

Задание

Легко

Давайте создадим текстовый редактор с поддержкой отмены действий, используя паттерн «Команда»! Вы создадите команды, инкапсулирующие операции с текстом, чтобы редактор мог выполнять действия и отменять их: это классический пример, показывающий, почему команды так удобны.

Вы распределите код по трём файлам:

  • command.go: Определите интерфейс команды и конкретные команды редактирования текста.

    Создайте интерфейс Command с двумя методами: Execute() string для выполнения действия и Undo() string для его отмены.

    Реализуйте два типа команд, работающих с *TextEditor:

    • InsertCommand: хранит указатель на редактор и текст для вставки. Execute добавляет текст в конец содержимого редактора и возвращает Inserted: [text]. Undo удаляет этот текст с конца и возвращает Undone insert: [text]
    • DeleteCommand: хранит указатель на редактор и количество символов для удаления с конца. Execute удаляет эти символы (сохраняя их для отмены) и возвращает Deleted: [removed text]. Undo восстанавливает их и возвращает Undone delete: [restored text]
  • editor.go: Создайте текстовый редактор (получателя) и исполнителя, который управляет историей команд.

    Создайте структуру TextEditor с полем Content (строка) и методами Append(text string) и DeleteLast(count int) string (возвращает удалённый текст).

    Создайте структуру EditorInvoker, которая хранит срез выполненных команд в качестве истории. Добавьте следующие методы:

    • ExecuteCommand(cmd Command) string: выполняет команду, добавляет её в историю и возвращает результат
    • UndoLast() string: удаляет последнюю команду из истории, вызывает её Undo и возвращает результат. Если история пуста, верните Nothing to undo
  • main.go: Продемонстрируйте работу системы команд с помощью серии операций.

    Считайте количество операций. Для каждой операции считайте её тип (insert, delete или undo). Для insert также считайте текст для вставки. Для delete считайте количество символов. Выполните каждую операцию через исполнителя и выведите результат. После выполнения всех операций выведите итоговое содержимое редактора в формате Final: [content].

Будут предоставлены следующие входные данные:

  • Строка 1: количество операций
  • Для каждой операции: тип операции, затем дополнительные данные, если они нужны (текст для insert, количество символов для delete)

Например, для входных данных:

5
insert
Hello
insert
 World
delete
3
undo
undo

Вывод должен быть следующим:

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

А для входных данных:

4
insert
Go
insert
Lang
undo
insert
!

Вывод должен быть следующим:

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

А для входных данных:

2
undo
insert
Test

Вывод должен быть следующим:

Nothing to undo
Inserted: Test
Final: Test

Обратите внимание: каждая команда инкапсулирует всё необходимое для выполнения и отмены своего действия. Исполнитель не знает, что делают команды. Он лишь выполняет их и ведёт историю, чтобы поддерживать отмену действий!

Попробуйте сами

package main

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

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// Считать количество операций
	line, _ := reader.ReadString('\n')
	numOps, _ := strconv.Atoi(strings.TrimSpace(line))
	
	// Создать редактор и объект для вызова команд
	editor := &TextEditor{}
	invoker := &EditorInvoker{}
	
	for i := 0; i < numOps; i++ {
		// Считать тип операции
		opLine, _ := reader.ReadString('\n')
		opType := strings.TrimSpace(opLine)
		
		var result string
		
		switch opType {
		case "insert":
			// Считать текст для вставки
			textLine, _ := reader.ReadString('\n')
			text := strings.TrimSuffix(textLine, "\n")
			
			// TODO: Создать InsertCommand и выполнить её через объект для вызова команд
			_ = text
			_ = editor
			
		case "delete":
			// Считать количество символов для удаления
			countLine, _ := reader.ReadString('\n')
			count, _ := strconv.Atoi(strings.TrimSpace(countLine))
			
			// TODO: Создать DeleteCommand и выполнить её через объект для вызова команд
			_ = count
			
		case "undo":
			// TODO: Вызвать UndoLast у объекта для вызова команд
			
		}
		
		fmt.Println(result)
	}
	
	// Вывести итоговое содержимое
	fmt.Printf("Final: %s\n", editor.Content)
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Go