Разработка
Шаблон проектирования «Команда»
В этой статье раскроем суть паттерна с примерами на Swift и Kotlin.
Шаблон проектирования «Команда» (Command) — это поведенческий паттерн, который превращает запрос или действие в объект. В мобильной разработке это один из самых полезных паттернов для реализации функций отмены действий (Undo/Redo), организации очередей задач и развязывания UI от бизнес-логики.
В этой статье раскроем суть паттерна с примерами на Swift и Kotlin.
Паттерн «Команда»
Шаблон Команда инкапсулирует запрос как объект, позволяя тем самым параметризовать клиенты различными запросами, ставить запросы в очередь или протоколировать их, а также поддерживать отмену операций.
В контексте iOS и Android этот паттерн часто становится спасением, когда вы сталкиваетесь с перегруженными контроллерами (Massive View Controller/Activity), где обработка нажатий смешана с бизнес-логикой.
Зачем использовать?
- Undo/Redo: Самый частый кейс. Если каждое действие пользователя (например, «удалить письмо», «наложить фильтр») — это объект
Command, то отмена действия — это просто вызов методаundo()у последнего объекта в стеке истории. - Очереди задач (Task Queue): Команды можно сохранять в список и выполнять последовательно или в фоновом потоке. Это полезно для синхронизации данных или выполнения сетевых запросов.
- Макрокоманды: Можно объединить несколько команд в одну (Composite Command), чтобы одним нажатием выполнить цепочку действий (например, «Сохранить и Выйти»).
Структура паттерна
Основная идея: отделить объект, который инициирует действие, от объекта, который это действие выполняет.
Вместо прямого вызова метода мы создаём объект-команду, который:
- знает, что нужно сделать;
- знает, над кем выполнить действие;
- может быть выполнен, сохранён, отменён или повторён.
Паттерн разделяет процесс на четыре основных участника:
- Command (Интерфейс): Объявляет метод для выполнения (обычно
execute()) и отмены (undo()). - Concrete Command (Конкретная команда): Реализует интерфейс. Связывает действие с получателем (
Receiver). - Receiver (Получатель): Объект, который непосредственно выполняет бизнес-логику (например,
DocumentEditorилиMusicPlayer). - Invoker (Инициатор): Объект, который инициирует выполнение (например, кнопка на экране или жест свайпа). Он не знает, как выполняется команда, он просто вызывает
execute(). - Client (Клиент) — создаёт команды и связывает их с получателями
Реализация на Swift (iOS)
Рассмотрим пример простого текстового редактора, поддерживающего отмену ввода.
import Foundation
// 1. Receiver: Тот, кто реально выполняет работу
class TextEditor {
private var text: String = ""
func append(_ newText: String) {
text += newText
print("Editor content: \(text)")
}
func delete(length: Int) {
text = String(text.dropLast(length))
print("Editor content: \(text)")
}
}
// 2. Command Protocol
protocol Command {
func execute()
func undo()
}
// 3. Concrete Command: Команда добавления текста
class AppendTextCommand: Command {
private let editor: TextEditor
private let textToAppend: String
init(editor: TextEditor, text: String) {
self.editor = editor
self.textToAppend = text
}
func execute() {
editor.append(textToAppend)
}
func undo() {
editor.delete(length: textToAppend.count)
}
}
// 4. Invoker: Менеджер команд (например, внутри ViewModel)
class CommandInvoker {
private var history: [Command] = []
func execute(_ command: Command) {
command.execute()
history.append(command)
}
func undo() {
guard let lastCommand = history.popLast() else { return }
lastCommand.undo()
}
}
// Использование
let editor = TextEditor()
let invoker = CommandInvoker()
let command1 = AppendTextCommand(editor: editor, text: "Hello, ")
let command2 = AppendTextCommand(editor: editor, text: "World!")
invoker.execute(command1) // Editor content: Hello,
invoker.execute(command2) // Editor content: Hello, World!
invoker.undo() // Editor content: Hello,
В данном примере CommandInvoker ничего не знает о том, как работать со строками, он просто управляет историей команд.
Реализация на Kotlin (Android)
В Android паттерн часто используется для обработки сложных пользовательских взаимодействий. Реализуем пример управления «Умным домом» (включение/выключение света).
// 1. Command Interface
interface Command {
fun execute()
fun undo()
}
// 2. Receiver: Устройство
class SmartLight(private val location: String) {
fun turnOn() {
println("$location light is ON")
}
fun turnOff() {
println("$location light is OFF")
}
}
// 3. Concrete Commands
class LightOnCommand(private val light: SmartLight) : Command {
override fun execute() = light.turnOn()
override fun undo() = light.turnOff()
}
class LightOffCommand(private val light: SmartLight) : Command {
override fun execute() = light.turnOff()
override fun undo() = light.turnOn()
}
// 4. Invoker: Пульт управления или экран приложения
class RemoteControl {
private val commandHistory = ArrayDeque<Command>()
fun submit(command: Command) {
command.execute()
commandHistory.addLast(command)
}
fun undo() {
if (commandHistory.isNotEmpty()) {
commandHistory.removeLast().undo()
}
}
}
// Использование
fun main() {
val livingRoomLight = SmartLight("Living Room")
val remote = RemoteControl()
val turnOn = LightOnCommand(livingRoomLight)
remote.submit(turnOn) // Output: Living Room light is ON
remote.undo() // Output: Living Room light is OFF
}
В Kotlin этот паттерн отлично сочетается с функциональными типами. Например, вместо создания классов команд, иногда можно передавать лямбды () -> Unit как упрощенную версию команд, если не требуется undo.
Преимущества и Недостатки
Плюсы
- Принцип единственной ответственности (SRP): Отделяет классы, вызывающие операции, от классов, выполняющих эти операции.
- Принцип открытости/закрытости (OCP): Можно добавлять новые команды, не ломая существующий код получателей или инициаторов.
- Композиция: Позволяет собирать сложные команды из простых (макрокоманды).
- Контроль: Легко реализовать логирование, отмену и повтор действий.
Минусы
- Усложнение кода: Количество классов может значительно вырасти, так как для каждого действия создается отдельный класс. Для простых приложений это может быть избыточным (overengineering).
Command и архитектуры мобильных приложений
MVVM / MVI
- Команды могут представлять user intents
- Хорошо сочетаются с однонаправленным потоком данных
Clean Architecture
- Command может быть обёрткой над Use Case
- Упрощает передачу действий из слоя UI в домен
Redux-подобные подходы
- Команды похожи на Actions, но с инкапсулированным поведением
Когда применять в мобильных приложениях?
Используйте паттерн «Команда», если:
- Вам нужна функция Undo/Redo (текстовые редакторы, графические редакторы)
- Вы создаете приложение с отложенными действиями (например, задачи выполняются при появлении сети)
- Вы хотите реализовать транзакционное поведение (если одна из серии команд упала, можно откатить все предыдущие)
Когда не стоит применять:
- для простых CRUD-экранов
- если команды не имеют состояния
- когда достаточно обычных колбэков или лямбд
Итог
Шаблон проектирования Command — мощный инструмент для мобильной разработки, особенно в приложениях со сложным поведением и богатым пользовательским интерфейсом.
Он помогает структурировать обработку действий, сделать код гибким и расширяемым, упростить тестирование и поддержку.
При разумном использовании Command отлично вписывается в современные архитектуры Android и iOS-приложений.
-
Видео и подкасты для разработчиков3 недели назад
Разработка видеоредактора
-
Новости4 недели назад
Видео и подкасты о мобильной разработке 2025.50
-
Вовлечение пользователей2 недели назад
Инженерия уверенности: почему ваш онбординг, вероятно, слишком короткий
-
Видео и подкасты для разработчиков2 недели назад
От идеи к CVE: как находить уязвимости в Android

