Приятно снова встретиться с вами, товарищ по изучению шаблонов!😁 Это серия постов, которые я делаю, чтобы рассказать миру о шаблонах проектирования и о том, как они могут помочь вам в разработке программного обеспечения🧑‍💻

Я настоятельно рекомендую ознакомиться с моими предыдущими статьями, так как они многому научат не только о Шаблонах, но и соответствующих принципах ООП 🙌:

Как всегда, я предлагаю купить книгу O'Reilly Media, чтобы получить максимальную отдачу от этих шаблонов: https://www.oreilly.com/library/view/head-first-design/9781492077992/

Состав:

  • вступление
  • Проблема
  • Раздел отступления
  • Принципы дизайна, которым нужно следовать: null в этой статье
  • Окончательный код решения
  • Рисунок

❗️Перед глубоким погружением❗️ я буду повторять определенные вещи несколько раз, чтобы вы усвоили их. Меня очень смутил этот паттерн, и я не хочу, чтобы вы мучились так же, как я💪

Шаблон команды

вступление

Как говорится: «Написание программного обеспечения похоже на искусство🎎». И это правда. Если оглянуться на предыдущие статьи и поразмыслить над способами реализации той или иной логики — становится очевидным, что существует бесчисленное множество способов переформулировать задачу и записать ее в том или ином подходе. Когда разница в подходе не коннотирует неполноценность , а чистое искусство🎨

А иногда один паттерн может нарушать принципы, проповедуемые в другом, как это было с Singleton, но это отступление, необходимое в конкретном случае, а не недостаток✨

Проблема

Представьте, что у вас есть объект, например пульт дистанционного управления. Он имеет несколько кнопок, которым можно назначить определенные действия. У вас может возникнуть очевидный вопрос: «Хм, кнопки можно переназначать, что означает, что мы не можем жестко закодировать объекты для кнопок». Тогда можно продолжить: «Значит, у нас должен быть какой-то интерфейс или абстрактная штука, которая позволит нам сваливать туда действие, а она распутает его и сделает нужные действия».

Ваш хотя процесс был правильным👍🏼 Более того, представьте, что эти действия будут выполняться на классах, которые нам предоставляет клиент.

Тааак:

            Remote Control Device
              * button1
              * button2
              * next buttons...
           
 'provided class1'  'provided class2'  'provided class n'           

Нам нужно создать дизайн, который будет передавать вызов по кнопке в классы и выполнять действие⚙️

Чтобы немедленно отбросить плохие идеи:

  • методы этих классов названы по-разному -› нет прямого вызова
  • классы могут быть добавлены или удалены -› никакого if/else беспорядка

Очевидно, мы воспользуемся Шаблоном команд, чтобы решить эту проблему.

Суть шаблона: отделяет запрос действия от объекта, который выполняет действие.

Где:

  • запрос: исходит от кнопки на удаленном устройстве.
  • object: класс, предоставленный нам клиентом.

Я знаю, что это звучит размыто, объясню подробнее🤚🏼:

  1. У нас есть устройство, которое можно считать API
  2. Он загрузил Команды (внутри кнопок)
  3. Эти команды инкапсулируют
    - modus operandi (способ выполнения) задачи
    - объект, который будет выполнять задачу.

Раздел отступления

Я знаю, что это может показаться странным на первый взгляд, по крайней мере, для меня это было так. Давайте разберемся на более приземленном примере, чтобы понять основные моменты. Представьте, что у нас есть ресторан:

  1. Клиент делает запрос: мы нажимаем кнопку на устройстве
  2. Официантка принимает запрос в форме заказа: Invoker принимает сигнал
  3. Порядок инкапсулирует объект для выполнения действия и способы выполнения действия: команда имеет объект для использования и знает, как это сделать то, что на нем
  4. Официантка «запускает» приказ, чтобы поручить повару выполнить работу: команда использует методы объекта, чтобы заставить их работать

=> Официантка (команда) отделена от повара (предоставляется классы)

А теперь посмотрим на схему:

1. Client creates the Command 
2. Client sets the Command in the Invoker object
   (not simply in the Invoker!!)
3. Client triggers the Invoker
4. Invoker triggers Command method
5. Method leverages receiver and calls methods belonged to it

Receiver — объект/класс, предоставленный клиентом (в нашем случае), который мы инкапсулируем в команду.

Команда — вещь, состоящая из приемника и набора действий, применяемых к получателю. И чтобы разделить все, у нас есть тот же метод, обычно называемый execute() , для запуска команды из API.

Invoker — вещь, которая просто запускает команду. В нашем случае это кнопка.

Итак, возвращаясь к примеру с нашим кафе:

  • Клиент Клиент (извините за тавтологию)
  • Официантка является Инвокером
  • Порядок — это команда
  • Кук является получателем

‼️Не торопитесь‼️ и перечитывайте все с нуля в этом разделе. Я часами вбивал это себе в голову (надеюсь, у вас получится быстрее)💨

  • Далее, каждая команда сама по себе не является командой, а реализует конкретный интерфейс. Мы назовем это Конкретная команда.
  • Эта конкретная команда связывает приемник и набор действий над ним, предоставляя execute() — аналогичный метод.

Таким образом, запрос — это не вещь сама по себе, это концепция, которая означает, что вызов Client запускает Invoker, который использует Command из Invoker Object. И эта команда имеет получатель и набор действий (где действия скрыты за execute() ).
Вот почему он называется получатель запроса.

Обсуждений и примеров может быть еще больше, но я думаю, что все хорошо описал. Дальнейшие объяснения будет проще сделать в коде. Вот почему я хотел пригласить вас в следующую рубрику🤝

Я постарался сделать максимально подробное изложение, но если вы чего-то не поняли, не стесняйтесь оставить комментарий. Я приду на помощь💁🏻‍♂️

Окончательный код решения

По следующей ссылке вы можете найти мой полный код шаблона, написанного на Kotlin.



А пока я хотел бы показать, как паттерн Command выглядит в коде и соответствует моим пояснениям сверху коду🤙

Небольшой шаг в сторону, чтобы поговорить о MacroCommand и Отменить:

  1. MacroCommand — это общая команда, которая включает в себя несколько команд. Итак, мы назначаем его кнопке, как обычно, но execute() будет запускать действия на нескольких получателях, проходя по списку/массиву команд, которые мы поместили в него.
  2. Отменить — это откат действий к предыдущему состоянию. Он реализует тот же интерфейс Command, но нам не нужно назначать его извне. Почему?🧐 Я расскажу об этом позже, пожалуйста, наберитесь терпения🙏

Сам код:

  1. RemoteLoader.kt – это файл, который создает наши команды и создает экземпляр устройства удаленного управления
    -Command.kt – это интерфейс для всех команд, которые мы будем использовать (помните, как я подчеркивал важность одного интерфейса для всех кнопки). Он имеет 2 метода: execute() & undo()
    — в блоке init {} setCommand() мы назначаем конкретную команду объекту Invoker (напомним этот термин из объяснения)
  2. RemoteControl.kt — это наше устройство дистанционного управления.
    - Он имеет 2 объекта, в которых мы храним команды включения и выключения. Мы можем назвать их Invoker Objects (опять же, вспомните выше)
    onButtonPush() и offButtonPush() — это наши Invokers. Мы указываем номер используемого слота, он берет из него команду и запускает execute()
    - Также:у нас есть команда Отменить. Еще позже плз😩
  3. Light.kt , CeilingFan.kt — наши клиентские классы. Они получатели
  4. LightOffCommand.kt , LightOnCommand.kt — это наши команды, которые реализуют командный интерфейс
    — у нас есть экземпляр получателя внутри каждой команды
    — любимый execute() который запускает методы на получателе
    - undo() (НАКОНЕЦ) использует действие, противоположное команде. Например, LightOnCommand вызывает light.on() в execute(), но в undo() у нас есть light.off()

Ниже я сделаю еще несколько комментариев:

  • В RemoteControl.kt у нас есть undoCommandPush(), который принимает последнюю назначенную команду на undoCommand и использует ее. т.е. у нас LightOnCommand выполняется последним, поэтому мы назначаем его undoCommand и undo() этой команды для запуска light.off()
  • NoCommand.kt — это простой способ поместить фиктивные объекты в объекты Invoker, которые ничего не делают в методах (да, они реализуют командный интерфейс для совместимости ), но гарантирует, что у нас есть что-то, что предотвратит сбой системы, если кнопка была вызвана и ей ничего не назначено.

Ради упражнения попробуйте распутать invokerCeilingFan.kt с соответствующими командами. Если вам трудно, оставьте комментарий!😇

* Шаблон команды разъединяет объект, делающий запрос, от объекта, который знает, как его выполнить
* Сама команда выполняет развязку.

Рисунок

На этой картинке вы можете наблюдать 2 части:

  • левый посвящен потоку узора
  • правый - сам узор

Оставьте сообщение в комментариях, если хотите, чтобы я прошёл через это👣

Аутро👋

Академическое определение Шаблон команды: он инкапсулирует запрос в виде объекта (команды), что позволяет параметризовать другие объекты с другим запросом.

На данный момент я не оставлю страшное😱 академическое определение как есть и раскрою его более подробно:
1. Команда инкапсулирует запрос, как? У него есть приемник и примененные к нему действия. Запрос — это концепция, означающая, что мы используем команду из объекта Invoker
2. Команды предоставляют похожие метод во внешний мир, срабатывание которого приведет к активации этих действий.
3. Внешний мир не имеет понятия, какой приемник был запущен. Они просто знают, что если они активируют execute() -›, все будет работать как чудо.
4. Что такое «параметризованные» вещи? Мы можем загрузить любую команду в объект Invoker (опять же, не просто Invoker, а это объект) до тех пор, пока это command реализует интерфейс команды.

Немного заключительной мысли☝️:

  • Шаблон команды может использоваться в запросах в очередь, т. е. мы помещаем команду в очередь, поток извлекает ее и запускает execute() подобный метод. Таким образом, нам не нужно изобретать велосипед, и с помощью этого интерфейса можно инкапсулировать совершенно разные операции.

Даа, было тяжело😪 Перечитайте несколько раз, если нужно, и пишите в комментариях, если что-то осталось неясным✌️

Ты можешь меня найти: