Паттерн Команда
Часть раздела Объектно-ориентированное программирование путешествия по 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 идеально подходит для реализации систем отмены/повтора, очередей задач или записи макросов, когда нужно сохранять, откладывать или воспроизводить операции.
Задание
ЛегкоДавайте создадим текстовый редактор с функциональностью отмены действий с использованием паттерна 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)
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП в Go
Внешние файлыРабочее пространство и модули GoПакеты и импортыЭкспортируемые и неэкспортируемые именаВведение в ООП в GoСтруктуры как классыОпределение методов структурПолучатели-указатели и получатели-значенияИнициализация структурФункции-конструкторыИтоги — Простой калькулятор4Интерфейсы
Введение в интерфейсыНеявная реализацияИнтерфейс как контрактПустой интерфейс (any)Утверждение типаПереключатель типовКомпозиция интерфейсовИнтерфейсы Stringer и ErrorПовторение: Калькулятор фигур7Инкапсуляция
Экспортируемые и неэкспортируемые поляИнкапсуляция на уровне пакетовГеттеры и сеттерыСокрытие информации в GoИтоги — Записи о студентах10Обобщения (Generics) (Go 1.18+)
Введение в GenericsПараметры типовОграничения типовОбобщенные структурыОбходной путь для обобщенных методовИтоги — Обобщенная коллекция13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн Abstract FactoryПаттерн ObserverПаттерн Strategy2Глубокое погружение в типы и структуры
Базовые и составные типыОпределение пользовательских типовТеги структурАнонимные структурыВложенные структурыНулевые значения и значения по умолчаниюПовторение — Контактная книга5Композиция вместо наследования
Почему в Go нет наследованияОсновы встраивания структурПродвижение методовВстраивание нескольких структурВстраивание против агрегацииЗатенение встроенных методовИтоги — Иерархия сотрудников8Обработка ошибок и ООП
Интерфейс errorПользовательские типы ошибокОбертывание ошибок (fmt.Errorf)Sentinel-ошибкиerrors.Is() и errors.As()Panic, Defer и RecoverИтоги — Парсер файлов11Стандартная библиотека и ООП
io.Reader и io.Writersort.InterfaceИнтерфейс fmt.Stringerencoding/json со структурамиИнтерфейс http.HandlerПовторение: модели REST API14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикMiddleware как Декоратор3Указатели и память
Основы указателей в GoУказатели на структурыПередача по значению и по ссылкеФункция new()Сборка мусора в GoПовторение: Конструктор связного списка6Полиморфизм в Go
Полиморфизм через интерфейсыУтиная типизация в GoПравила реализации интерфейсовПолиморфные коллекцииВнедрение зависимостейИтоги — Обработчик платежей9Конкурентность и ООП
Основы горутинКаналы и взаимодействиеБуферизованные и небуферизованные каналыОператор selectsync.Mutex и sync.RWMutexsync.WaitGroupПроектирование потокобезопасных структурПовторение — Worker PoolПотренируйтесь самостоятельно: Онлайн-компилятор Go