Паттерн Command (Команда) инкапсулирует запрос в виде объекта, позволяя параметризовать клиентов различными запросами, ставить их в очередь, логировать и поддерживать отмену операций. В приложениях на Vue.js этот паттерн особенно полезен для управления действиями пользователя, бизнес-логикой и сложными сценариями изменения состояния.
Вместо прямого вызова методов из компонентов логика действий выносится в отдельные объекты-команды. Компонент знает только о том, что команда существует и может быть выполнена.
Компоненты Vue часто начинают с простых обработчиков:
methods: {
save() {
this.$store.dispatch('saveData')
},
deleteItem() {
this.$store.dispatch('deleteItem', this.id)
}
}
Со временем возникают сложности:
Command паттерн устраняет эти проблемы за счёт изоляции действий.
Классическая команда состоит из:
В JavaScript команда обычно реализуется как класс или объект с методами.
class Command {
execute() {}
undo() {}
}
Получатель — это сервис, Vuex-модуль или composable, содержащий бизнес-логику.
class TodoService {
add(todo) {
// добавление задачи
}
remove(id) {
// удаление задачи
}
}
class AddTodoCommand {
constructor(service, todo) {
this.service = service
this.todo = todo
}
execute() {
this.service.add(this.todo)
}
undo() {
this.service.remove(this.todo.id)
}
}
Команда полностью описывает действие и не зависит от Vue-компонента.
import { AddTodoCommand } from '@/commands/AddTodoCommand'
import { todoService } from '@/services/TodoService'
export default {
methods: {
addTodo(todo) {
const command = new AddTodoCommand(todoService, todo)
command.execute()
}
}
}
Компонент:
Command паттерн хорошо сочетается с Vuex, особенно если требуется управление историей действий.
class IncrementCounterCommand {
constructor(store) {
this.store = store
}
execute() {
this.store.commit('increment')
}
undo() {
this.store.commit('decrement')
}
}
Для поддержки undo/redo используется менеджер команд.
class CommandManager {
constructor() {
this.history = []
}
execute(command) {
command.execute()
this.history.push(command)
}
undo() {
const command = this.history.pop()
if (command && command.undo) {
command.undo()
}
}
}
Менеджер может быть:
export const commandManager = new CommandManager()
commandManager.execute(
new IncrementCounterCommand(store)
)
В Composition API команды удобно использовать вместе с composable-функциями.
export function useCommands() {
const manager = new CommandManager()
const run = (command) => manager.execute(command)
const undo = () => manager.undo()
return { run, undo }
}
Команды остаются независимыми от Vue, а orchestration выполняется через composable.
Паттерн Command позволяет легко добавлять дополнительные возможности:
class LoggingCommand {
constructor(command) {
this.command = command
}
execute() {
console.log('Execute', this.command)
this.command.execute()
}
undo() {
console.log('Undo', this.command)
this.command.undo()
}
}
Макрокоманда объединяет несколько команд в одну.
class MacroCommand {
constructor(commands) {
this.commands = commands
}
execute() {
this.commands.forEach(cmd => cmd.execute())
}
undo() {
[...this.commands].reverse().forEach(cmd => cmd.undo())
}
}
Используется для сложных пользовательских сценариев, например, мастеров или транзакций.
Command паттерн особенно полезен в Vue-приложениях:
В простых CRUD-формах он может быть избыточен, но в масштабируемых системах становится архитектурным фундаментом.