Зачем нужен?
Паттерн Command применяется, когда необходимо выполнять команды(операции, запросы), без знания того, какие функции должны вызываться и даже того, кто должен выполнять эти функции. Паттерн инкапсулирует вычислительные блоки(получателя + набор операций). Его можно применять в планировщиках, различных пулах, очередях задач.
Обеспечивает соблюдение принципа инверсии зависимостей. Уменьшает связанность классов.
Определение
- инкапсулирует запрос в виде объекта, делая возможной параметризацию клиентских объектов с различными запросами, организацию очереди или регистрацию запросов, а также поддержку отмены операций
Реализация
Command разъединяет(decouple) объект, который вызывает операцию от объекта, который знает как эту операцию выполнить. Для этого создается интерфейс Command, который связывает получателя с действием. Интерфейс содержит абстрактный метод execute
, который вызывает действия у получателя. Клиенты Command рассматривают получателя как черную коробку: когда необходимо, клиент вызывает метод execute
и больше его ничего не волнует.
Клиент, создающий команду и клиент, выполняющий команду - разные клиенты. Данное разделение обеспечивает гибкость во времени выполнения и последовательности команд. Инкапсуляция команд в виде объекто делает возможным их передачу/хранение/загрузку и тд.
- Определите интерфейс команды с сигнатурой метода такой как
execute()
- Создайте несколько конкретных классов комманд, содержащих объект получателя, методы, которые нужно вызвать и аргументы, которые нужно передать получателю.
- Инициализируйте объекты команд для каждого отложенного выполнения
- Передайте объекты команд от создателя(отправителя, sender) к инициатору(invoker)
- Инициатор определяет, когда выполнять команды
Пример
Допустим у нас есть пульт управления умным домом. В нем есть возможность задать несколько устройств, и для каждого устройства есть кнопка on и off.
Определим интерфейс команды
// абстрактная команда, используем для инкапсуляции классов устройств(устройство + операции)
// в результате при добавлении новых классов устройств
// не нужно будет изменять управляющий класс пульта(remoteControl)
protocol Command {
func execute()
}
Создадим несколько конкретных классов команд
// классы конкретных комманд, инкапсулируют запросы к конкретным устройствам
class NoCommand: Command {
// объект-получатель
func execute() {
print("No command")
}
}
class LightOnCommand: Command {
// объект-получатель
private let light: Light
init(light: Light) {
self.light = light
}
func execute() {
// методы, которые нужно вызвать, без аргументов
light.on()
}
}
class LightOffCommand: Command {
// объект-получатель
private let light: Light
init(light: Light) {
self.light = light
}
func execute() {
// методы, которые нужно вызвать, без аргументов
light.off()
}
}
class StereoOnWithCDCommand: Command {
// объект-получатель
private let stereo: Stereo
init(stereo: Stereo) {
self.stereo = stereo
}
func execute() {
// методы, которые нужно вызвать, без аргументов
stereo.on()
stereo.setCD()
stereo.setVolume()
}
}
class GarageOpenCommand: Command {
// объект-получатель
private let garage: Garage
init(garageDoor: Garage) {
self.garage = garageDoor
}
func execute() {
// методы, которые нужно вызвать, без аргументов
garage.openDoor()
}
}
class GarageCloseCommand: Command {
// объект-получатель
private let garage: Garage
init(garageDoor: Garage) {
self.garage = garageDoor
}
func execute() {
// методы, которые нужно вызвать, без аргументов
garage.closeDoor()
}
}
Инициализируем объекты команд
let livingRoomLight = Light(site: "Living Room")
let livingRoomLightOn = LightOnCommand(light: livingRoomLight)
let livingRoomLightOff = LightOffCommand(light: livingRoomLight)
let kitchenLight = Light(site: "Kitchen")
let kitchenLightOn = LightOnCommand(light: kitchenLight)
let kitchenLightOff = LightOffCommand(light: kitchenLight)
let garage = Garage()
let garageOpen = GarageOpenCommand(garageDoor: garage)
let garageClose = GarageCloseCommand(garageDoor: garage)
Передадим объекты команд от создателя(creator) к инициатору(invoker)
let remote = RemoteControl()
remote.setCommand(0, onCommand: livingRoomLightOn, offCommand: livingRoomLightOff)
remote.setCommand(1, onCommand: kitchenLightOn, offCommand: kitchenLightOff)
remote.setCommand(2, onCommand: garageOpen, offCommand: garageClose)
В нашем случае команды выполняются по нажатию кнопок на пульте управления
remote.onButtonPressed(0)
remote.offButtonPressed(0)
remote.undoButtonPressed()
remote.onButtonPressed(1)
remote.offButtonPressed(1)
remote.onButtonPressed(2)
remote.offButtonPressed(2)
remote.onButtonPressed(3)
remote.offButtonPressed(3)
В качестве другого примера можно рассмотреть ресторан. Командой в данном случае будет заказ. Посетитель - клиент-создатель(client, creator), официантка - инициатор, повар - получатель.