Menu
Coddy logo textTech

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

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

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

В 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()
}

Invoker полностью отделён от получателя (Light):

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! Вы создадите команды, которые encapsulates операции над текстом, позволяя редактору выполнять действия и отменять их: это классический пример, демонстрирующий, почему команды настолько powerful.

Вы организуете код в трёх файлах:

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

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

    Implement два типа команд, работающих с *TextEditor:

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

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

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

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

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

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

  • Строка 1: количество операций
  • Для каждой операции: тип операции, затем дополнительные данные, если необходимо (текст для insert, count для 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

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

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

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 и invoker
	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 и выполнить его через invoker
			_ = text
			_ = editor
			
		case "delete":
			// Прочитать количество символов для удаления
			countLine, _ := reader.ReadString('\n')
			count, _ := strconv.Atoi(strings.TrimSpace(countLine))
			
			// TODO: Создать DeleteCommand и выполнить его через invoker
			_ = count
			
		case "undo":
			// TODO: Вызвать UndoLast у invoker
			
		}
		
		fmt.Println(result)
	}
	
	// Вывести итоговое содержимое
	fmt.Printf("Final: %s\n", editor.Content)
}
quiz iconПроверьте себя

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

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

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