Command паттерн для действий

Паттерн Command (Команда) инкапсулирует запрос в виде объекта, позволяя параметризовать клиентов различными запросами, ставить их в очередь, логировать и поддерживать отмену операций. В приложениях на Vue.js этот паттерн особенно полезен для управления действиями пользователя, бизнес-логикой и сложными сценариями изменения состояния.

Вместо прямого вызова методов из компонентов логика действий выносится в отдельные объекты-команды. Компонент знает только о том, что команда существует и может быть выполнена.


Проблема прямых действий в компонентах Vue

Компоненты Vue часто начинают с простых обработчиков:

methods: {
  save() {
    this.$store.dispatch('saveData')
  },
  deleteItem() {
    this.$store.dispatch('deleteItem', this.id)
  }
}

Со временем возникают сложности:

  • логика разрастается и дублируется;
  • трудно реализовать отмену действий (undo/redo);
  • сложно логировать пользовательские операции;
  • компоненты становятся тесно связаны с хранилищем и сервисами.

Command паттерн устраняет эти проблемы за счёт изоляции действий.


Базовая структура Command

Классическая команда состоит из:

  • execute() — выполнение действия;
  • undo() — откат (опционально);
  • ссылки на получателя (receiver), который выполняет реальную работу.

В JavaScript команда обычно реализуется как класс или объект с методами.

class Command {
  execute() {}
  undo() {}
}

Реализация команды для Vue

Получатель (Receiver)

Получатель — это сервис, 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-компонента.


Использование команды в компоненте 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

Command паттерн хорошо сочетается с Vuex, особенно если требуется управление историей действий.

Команда, работающая с Vuex

class IncrementCounterCommand {
  constructor(store) {
    this.store = store
  }

  execute() {
    this.store.commit('increment')
  }

  undo() {
    this.store.commit('decrement')
  }
}

Менеджер команд (Invoker)

Для поддержки 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()
    }
  }
}

Интеграция менеджера в приложение Vue

Менеджер может быть:

  • глобальным сервисом;
  • частью Vuex-модуля;
  • composable-функцией.
export const commandManager = new CommandManager()
commandManager.execute(
  new IncrementCounterCommand(store)
)

Command и Composition API

В 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.js

  • строгая изоляция бизнес-логики от UI;
  • централизованное управление действиями;
  • удобная реализация undo/redo;
  • упрощённое тестирование;
  • расширяемость без изменения компонентов.

Когда паттерн оправдан

Command паттерн особенно полезен в Vue-приложениях:

  • с насыщенным пользовательским взаимодействием;
  • с редакторами, конструкторами, админ-панелями;
  • при необходимости истории изменений;
  • при сложных сценариях управления состоянием.

В простых CRUD-формах он может быть избыточен, но в масштабируемых системах становится архитектурным фундаментом.