Паттерны ООП простыми словами: паттерны поведения
В этой статье простыми словами поговорим про паттерны поведения, которые описывают, как должны происходить процессы с несколькими вариантами развития событий.
Простейший пример цепочки обязанностей – процесс получения любого официального документа. Допустим, вам нужно получить некую справку из банка. Вы приходите в отделение рядом с домом, но там только выдают и принимают деньги. Вы идете в более крупное отделение города, но оказывается, что там такие справки не выдаются. Так происходит, пока вы не добираетесь до регионального отделения, где вам, наконец, могут дать нужную справку.
Таким образом сформирована «цепочка обязанностей», отдельные объекты которой обрабатывают ваш запрос. Запрос может быть обработан и в первом отделении, в зависимости от типа запроса и объектов, которые его обрабатывают.
Паттерн «Команда» похож на то, как работают выключатели света в наших домах. Каждый выключатель делает простое действие – соединяет или разъединяет два контакта. Что стоит за этими контактами выключателю неизвестно. Этот паттерн просто определяет общие правила для объектов, без необходимости описания всей подоплеки. Благодаря этому, одним типом выключателей можно взаимодействовать как с освещением, так и с чайником.
«Итератор» предоставляет правила доступа к списку объектов вне зависимости от их типа. Пример в реальной жизни – школьная шеренга на физкультуре, рассчитывающаяся на первый-второй. Часто, этот паттерн используется для доступа к другому – «реестру».
Вернемся к примеру из паттерна «одиночка» – телефонной станции. Ее можно рассматривать в качестве релевантного примера и для данного паттерна. Однако есть различия: «посредник» должен еще и управлять своей группой. Если вернуться к примеру со строем, то «медиатором» в нем будет командир, отдающий приказы.
Этот паттерн описывает, подходит ли данный объект по параметрам. К примеру, нужно погрузить на судно грузовые контейнеры. Чтобы понять, нужно ли грузить конкретный контейнер, нам нужно выбрать метод, который будет это определять – этот процесс будет реализован паттерном «спецификация». В простейшем случае для каждого контейнера в «спецификации» мы определим страну назначения и если она будет совпадать со страной прибытия корабля – будем грузить. Таким образом, один раз вводится правило «сравнить две страны назначения», которое будет применяться ко всем кораблям и контейнерам.
Является прямым следствием «спецификации». Позволяет распределять объекты по категориям на основе определенных условий. Примером категоризации по аналогии с контейнерами и кораблями будет то, какие контейнеры в какие страны направляются.
Вспомните тот момент, когда вы просили знакомого человека рядом записать в свой мобильный номер, который вам диктуют по телефону. В этот момент ваш товарищ реализовал паттерн «хранитель».
В общих случаях он нужен, когда какому-то объекту требуется сохранить свое состояние в другом объекте и при необходимости восстановить его.
Данный паттерн – это подписка на событие. К примеру, если вы подписаны на смс или email рассылку – ваш телефон или почтовый ящик реализует паттерн «Listener».
Данный паттерн является расширением предыдущего. Blackboard позволяет централизовано обслуживать как «наблюдателя», так и создателя событий. По аналогии с прошлым примером, этот паттерн реализует сам сайт, на котором был подписан пользователь и который генерирует рассылки.
Servant служит для предоставления группе объектов общего функционала. Например, сайт с рассылкой из прошлого примера может быть для своих пользователей еще и «слугой», предоставляя, например, на главной странице информацию о точном времени или погоде, помимо основной рассылки.
«Посетителя» легко понять, если представить стационарное обследование в больнице. Здесь посетителями будут врачи, которые приходят к пациенту и осматривают его. Мы можем использовать один и тот же алгоритм, чтобы отправлять разных врачей к разным пациентам.
Частный случай предыдущего паттерна. В рамках прошлого примера, врач будет не один и тот же, а каждый раз другой. То есть, мы отправляем к пациенту не врача, который работает в больнице, а каждый раз нанимаем самостоятельного доктора, который делает свою работу, получает плату и уходит.
Это тот же врач-«посетитель», только он отправляется в больницу и обходит всех больных, а не только одного.
Можно привести в качестве аналогии внутреннее состояние человека – оно может быть разным в разный момент времени. Например, человек пришел с работы и получил запрос «Сходить в магазин». Такой запрос вызовет негативную выдачу «не пойду». Однако, если завтра человеку никуда не надо, возможно, он захочет купить в магазине не только хлеб насущный и результат обработки запроса будет ровно противоположным. Итого: один объект, один запрос, разные результаты. Для определения результата и используется «состояние».
Человек приходит в магазин и имеет такие входные условия: нужно много, денег мало. В таком случае поиск нужных товаров займет много времени, чтобы они подошли по цене. В случае, если нужно много и денег тоже много, то в корзину будут попадать первые приглянувшиеся товары и времени на покупки уйдет меньше. Этот человек в магазине реализовывает паттерн «стратегия»: в зависимости от заданных параметров он выбирает стратегию расходования ресурсов для выполнения задачи.
как поменять привычки и воспитать силу воли
Почему спустя месяц после Нового года никто не вспоминает о своих новогодних резолюциях? Почему так тянет в McDonald’s, даже если в приоритете забота о здоровье? Почему даже при реальной угрозе смерти изменить образ жизни получается только у 14% людей? Наши друзья из проекта Reminder изучили тему, чтобы понять, как управлять привычками по науке и стать лучшей версией себя. Для этого они поговорили с Алексеем Ивановым, дизайнером продуктов в калифорнийском стартапе Directly, автором телеграм-канала Ponchik News, коучем и пропагандистом поведенческого дизайна.
Фото: John Arano / Unsplash
Что такое дизайн поведения?
Поведенческий дизайн, или дизайн поведения, — комплекс упрощенных методик, заимствованных из поведенческих наук. Они помогают целенаправленно изменять привычки и работать над собственным поведением.
Эти методики используются в бизнесе и помогают выпускать продукты, к которым пользователи легко привыкают. Именно благодаря использованию знаний, которые подарили нам поведенческие науки, пользователей так и тянет лишний раз проверить Facebook или смартфон. Но ту же технику можно использовать во благо.
Например, это сделал исследователь из Корнелльского университета Ричард Паттерсон. Он начал искать решение, чтобы повысить процент студентов, которые завершают онлайн-курсы. Обычно это делают лишь 10% студентов.
Паттерсон предложил части студентов установить ежедневный лимит на интернет. Когда студенты тратили лимит, программа RescueTime блокировала сайт. Каждое утро они получали электронное письмо с напоминанием об их ограничении. По сравнению с контрольной группой эти студенты получили более высокие оценки и потратили на изучение курса на 24% больше времени. Они завершали курс с вероятностью 40% (сравним с исходными 10%!). По словам Паттерсона, успешным студентам помог «механизм принятия обязательств» (commitment device) из поведенческой экономики: если человек заранее устанавливает ограничение, это увеличивает его терпение и силу воли.
На привычках основаны около 40% наших ежедневных действий. Более того, современные исследования ставят под вопрос даже нашу свободу в принятии решений. В 2008 году нейробиологи сканировали мозг испытуемых, чтобы понять, как люди принимают решения. Участники эксперимента должны были нажимать кнопку левой или правой рукой, какой именно — им предстояло решить самим. Оказалось, что по активности мозга можно понять, какой рукой воспользуется участник. Но самое удивительное, что выяснили исследователи: определенные паттерны мозговой активности подсказывают поведение еще до того, как человек сознательно примет решение, — за целых семь секунд. И если огромная часть того, что мы делаем, происходит автоматически и почти бессознательно, то и корректировать действия можно не «волевым» усилием, а через те же поведенческие механизмы.
Каким образом решение превращается в автоматическое поведение? Нейробиологи из Массачусетского технологического института экспериментировали на крысах и выяснили, что при повторении одних и тех же действий мозговая активность меняется.
Крыс запускали в лабиринт, в конце которого их ждала награда — кусочек шоколада. В начале эксперимента их мозг, особенно базальное ядро, работал активно. Но спустя некоторое количество повторений мозговая активность уменьшилась: пики были только в начале и в конце лабиринта. Крыса сразу устремлялась по знакомому пути к шоколаду, при этом центры мозга, которые ответственны за принятие решений и даже память, «спали». Руководило крысой базальное ядро — именно там, в нейронах полосатого тела, «хранятся» привычки.
Анализ активности мозга крыс показал, что тот превращал последовательные действия в привычки не случайно, а по определенной схеме. Механизм можно описать в виде модели: знак (щелчок, который открывал лабиринт) дает старт действию, действие происходит (движение по лабиринту), мозг получает награду (шоколад).
Итак, привычка = знак (триггер) + рутинное действие + награда.
Чуть усложним ситуацию. На самом деле через некоторое количество повторений крыса уже знала, что в конце лабиринта ждет шоколад. Когда раздавался щелчок и пахло шоколадом, крыса бежала вперед, одержимая желанием получить награду. Поэтому, чтобы понять, как мы привыкаем к тем или иным действиям, добавим в уравнение страстное желание.
Цикл привычки = видим ЗНАК → страстно ЖЕЛАЕМ награду → делаем ДЕЙСТВИЕ → чтобы получить НАГРАДУ.
Сканирование человеческого мозга подтвердило, что тот же простой неврологический цикл верен и для людей. Формулу назвали петлей привычки.
Вот так могут сформироваться типичные привычки.
- Вы проснулись → хотите почувствовать себя бодрым → пьете кофе → чувствуете себя бодрым.
Вы удовлетворили желание, кофе становится связанным с пробуждением. - Вы чувствуете запах пончиков, когда идете на работу, → хотите пончик → покупаете пончик и едите его → удовлетворили желание съесть пончик.
Теперь покупка пончика связана с дорогой на работу, и есть риск, что вы будете покупать его все чаще. - Вы столкнулись со сложностью на работе → хотите избавиться от разочарования → достаете телефон и проверяете соцсети → чувствуете облегчение.
Сложности в работе регулярно вызывают желание залезть в телефон.
Автоматические действия могут быть простыми и состоять из одной конкретной активности (сказать «спасибо» в ответ на комплимент), а могут быть устроены весьма сложно (одна парковка машины состоит из десятка разных решений, которые опытный водитель выполняет «на автопилоте»). В конце концов, даже наше мышление — привычка: например, негативным мыслям также соответствуют определенные нейронные модели.
Чтобы спроектировать привычку, Алексей Иванов, дизайнер продуктов в калифорнийском стартапе Directly, предлагает связать награду с одним из эволюционно значимых механизмов нашей психики. «Эволюционные механизмы заставляют нас не только есть, спать и заниматься сексом: они и про кооперацию, и про социальный статус, и про валидацию в группе, — объясняет он. — Это знают и используют разработчики приложений».
Полученные лайки высвобождают нейротрансмиттер дофамин, который отвечает за ощущение удовольствия. В этом случае цикл формирования привычки завязан на потребность быть частью социума.
Дофамин — только один из нейромедиаторов системы внутреннего подкрепления, которая представляет целую нейронную структуру мозга. Она активизируется, когда мы делаем что-то субъективно приятное — и чем легче это приятное достается, тем быстрее мозг запоминает: таким путем можно получить поощрение. Поэтому нужно совсем немного времени, чтобы привычка сформировалась, — и вот вы уже «зависимы» от приложения.
Наградой, которая вызовет правильную нейрохимическую реакцию лимбической системой и поможет «закрепить» поведение, может быть еда — как в экспериментах с крысами. Это самый базовый эволюционный мотиватор, которым можно «подкрепить» желаемое поведение.
Например, cигнал будильника (триггер) — встать вовремя (действие) — самый вкусный завтрак (награда). Для большего эффекта добавьте в «награду» продукты, которые естественным образом увеличивают уровень «счастливых» нейромедиаторов в организме: сою, ананасы, бананы, артишоки, шоколад и сыр (аминокислота триптофан
Другие «награды» с нейрохимической подоплекой.
- Движение, причем не только изнуряющие упражнения на высоком пульсе, а даже медитативная растяжка стимулирует синтез эндорфина.
Еще помогут смех и слезы. - Социальное поощрение, или похвала, дает дозу серотонина и мотивирует добиваться высоких целей, чтобы снова и снова заслужить уважение и признание. «Я веду канал и выступаю с лекциями на интересные мне темы. Это восполняет мою потребность в социальном одобрении. Это мой способ себя проявлять», — комментирует эту «награду» Иванов.
- Принадлежность к группе связана с окситоцином и обеспечивает нам чувство безопасности. Одновременно работает и «серотониновое» социальное поощрение — получается нейрохимическое комбо. Неважно, хотите вы бегать по утрам, худеть или учить испанский: это легче сделать, присоединившись к соответствующему сообществу.
- Достижение цели, особенно — сложной, вызывает синтез дофамина. Поэтому не забывайте отмечать даже скромные успехи во внедрении привычек. Для этого Алексей Иванов советует трекер Habit, в котором можно настроить напоминания, отметить проделанную активность и отследить, как часто вы ее выполняете. Чуть более навороченный Tally — он позволяет внедрять новое поведение вместе с другом или подругой (см. пункт «Принадлежность к группе»).
Уже усвоенную привычку можно изменить. Само знание о том, как формируется привычка, облегчает процесс: так, в Колумбийском университете и Университете Альберты провели серию экспериментов по формированию привычки к физическим упражнениям. Половине участников рассказали про «петлю привычки» и предложили придумать для себя триггеры и награды, которые помогут им регулярно заниматься спортом. В следующие четыре месяца те участники, которые узнали про цикл формирования привычек и определили триггеры и награды, потратили вдвое больше времени на тренировки, чем те, кто ничего о привычках не знал.
Вернемся к крысами из эксперимента Массачусетского университета. Когда награду из лабиринта убрали, крысы отказались от привычки бегать в поисках шоколада. Но, стоило ее вернуть, как вернулась и привычка.
Почему так произошло? Старые привычки не забываются, они навсегда «зашиты» в мозге, а каждое повторение их усиливает. Это объясняет, почему нам так сложно от них отказаться.
Чарлз Дахигг, обладатель Пулитцеровской премии за статьи о технологичных компаниях и культовый дизайнер поведения, описывает «золотое правило» избавления от вредных привычек так: бессмысленно пытаться их уничтожить, нужно их изменить.
«У меня появилась плохая привычка ходить в кафетерий каждый день и есть шоколадное печенье, — вспоминает Дахигг собственный опыт. — Я положил на компьютер заметку с надписью NO MORE COOKIES. Но каждый день мне удавалось игнорировать эту записку».
Отказаться от печенья ни с помощью заметок, ни волевым усилием не получилось — но потом Дахигг изучил петлю привычки и осознанно заменил привычное действие и вознаграждение на менее вредные.
Чтобы изменить привычку, Дахигг советует:
- Определить привычное действие.
Для себя Дахигг сформулировал действие так: встать днем с рабочего места, пойти в кафетерий, купить шоколадное печенье и съесть его, общаясь с друзьями.
2. Поэкспериментировать с наградами.
Дахигг не знал, что именно приносило ему удовольствие: само печенье, прилив энергии от сахара в печенье или временная передышка от работы. Он начал последовательно экспериментировать: почувствовав желание выйти за печеньем, в разные дни он прогулялся по кварталу, ничего не съев; купил вместо печенья пончик, потом яблоко, потом кофе; вместо кафетерия пошел поболтать в кабинет к другу. Каждый раз спустя 15 минут он спрашивал себя: мне все еще хочется печенья? Так Дахигг понял, что ест печенье не потому, что голоден, — просто ему нужно отвлечься от работы.
Чтобы избавиться от нежелательной привычки:
- Найдите триггер, который запускает привычку.
Триггеры бывают внутренние (голод, скука, грусть) и внешние (уведомление на телефоне, реклама, запах выпечки). Иногда для желаемого поведения достаточно подобрать правильный триггер, а чтобы избавиться от нежелательного — удалить триггер, который его инициирует: например, отключить телефон, чтобы уведомление-триггер не отвлекало от работы.
Дахигг попытался понять, что является сигналом для каждодневной прогулки за печеньем. Для этого он письменно задал себе такие вопросы.
Где вы? (сижу за своим столом)
Который сейчас час? (3:36 вечера)
Каково ваше эмоциональное состояние? (скучно)
Кто еще здесь? (никто)
Какие действия предшествовали побуждению? (ответил на письмо)
Через три дня Дахигг понял: триггер — это время. Каждый день его тянуло к печенью между 15:00 и 16:00.
2. Распишите цикл для новой привычки.
Поняв рутинное действие, награду и триггер, Дахигг построил новый цикл привычки. Он написал:
«В 15:30 каждый день я буду ходить к партнеру и разговаривать 10 минут».
И установил будильник на 15:30. Через несколько недель новая привычка стала автоматической, и к печенью Дахигга больше не тянуло.
Не нужно сразу браться за все раздражающие привычки. Иногда достаточно изменить одну, ключевую привычку, которая научит вас перепрограммировать другие. Такой привычкой может быть занятие спортом: так, Дахигг упоминает, что регулярная физическая активность не только улучшает пищевое поведение, но и необъяснимым образом заставляет меньше пользоваться кредитными картами.
Повторяющиеся действия, которые мы совершаем снова и снова, — более сложный и развернутый вариант привычки. Их Алексей Иванов называет стратегиями. Адаптивная стратегия — такая модель поведения, которая помогает справляться со стрессом и приспосабливаться к изменениям среды. А неадаптивная — та, которая когда-то работала, но больше не эффективна.
«Печально, но факт: каждый из нас использует много неадаптивных стратегий поведения, а вот адаптивных — гораздо меньше, — говорит Алексей Иванов. — Хорошая новость: их можно менять».
Простейший пример неадаптивной стратегии — нарратив перфекционизма, когда человек не может расслабиться из-за беспокойства, что делает все не идеально. В результате он постоянно откладывает дела или переделывает то, что уже закончил. Более того, человек начинает считать, что он хорош и ценен настолько, насколько хороша его работа. Любая ошибка угрожает его самооценке — значит, он менее устойчив к стрессу. С перфекционизмом также часто сочетается так называемый «синдром самозванца», для которого, по словам нейробиолога доктора Тары Сварт, характерны высокий уровень кортизола и низкий уровень дофамина и серотонина — то есть много стресса, мало радости и мотивации что-то делать.
Перфекционизм приводит к расстройствам пищевого поведения и депрессии. И, несмотря на стремление к высоким результатам, перфекционизм не ведет к успеху: исследование 2010 года показало, что профессора психологии, одержимые стремлением к совершенству, меньше публикуются во влиятельных журналах, чем их более реалистичные коллеги.
«В перфекционизме есть две стороны: позитивная — про стремление к выдающемуся, негативная — про страх социального неодобрения, что кто-то тебя разоблачит, поймет, что ты не соответствуешь высоким критериям, — объясняет Алексей Иванов. — За негативной стороной также стоит эволюционный механизм: раньше было сложно выжить, если тебя исключали из группы, поэтому в нас так сильно закреплен страх быть отвергнутым».
Наша задача как дизайнеров поведения — преобразовать перфекционизм из злокачественного состояния, минимизировать страх социального неодобрения, но при этом сохранить мотивацию. Когда парализующее стремление к идеальному пройдет, человек заметит, что большинство задач на самом может быть решено с внутренней оценкой «достаточно хорошо».
Избавление от перфекционизма, как и любое другое изменение поведения, — индивидуальная работа, и нет универсальной инструкции, которая подойдет всем, говорит Алексей Иванов. Он работает с поведенческими паттернами по такой схеме, которая позволяет найти причинно-следственные связи и перепрограммировать их.
- Разобрать паттерн.
Стратегия перфекционизма может привести, например, к прокрастинации или к самоосуждению. Подумайте, что актуально именно в вашем случае.
2. Заметить, какой триггер «включает» действие.
В случае перфекционизма триггером может быть совершенная ошибка, критика коллег, тупик в важном проекте.
3. Заменить действие на другое.
Нужно найти менее деструктивное действие. Например, вместо самоосуждения дать себе право на три ошибки в день, вместо прокрастинации — планировать время и делегировать часть дел коллегам.
4. Понять, какая скрытая выгода (бессознательно получаемое преимущество) содержится в паттерне.
Например: «Осуждение ведет к тому, чтобы меня пожалели и полюбили, то есть мне это нужно, чтобы меня любили».
5. Дать себе скрытую награду другим способом.
Если хочется, чтобы вас похвалили, — попросите об этом кого-то из близких. Вряд ли они откажут.
Кроме последовательной проработки паттерна Алексей Иванов советует не сравнивать себя с другими. Вместо этого он предлагает сравнивать себя с собой же год, два или три назад — и применять практику благодарности: «Один раз в начале или в конце дня я благодарю трех людей, которые сделали для меня что-то хорошее, или три разных события, которые меня вдохновили, мотивировали или просто порадовали, — рассказывает он. — С помощью благодарности я переключаю мышление: вместо того чтобы сравнивать себя с кем-то, я наслаждаюсь тем, что есть у меня сейчас». Полезно также сделать практику письменной.
Эффективный дизайн поведения начинается с привычек, потом нужно определить неадаптивные стратегии и постепенно работать с ними. Но нужно их сначала найти: выяснить, какие истории человек о себе рассказывает, как он себя видит и какие у него на самом деле сильные и слабые стороны. Часто нежелательное поведение и отсутствие мотивации связаны именно с искаженным восприятием себя.
Для объективной оценки личности Алексей Иванов использует психометрию и составляет психологический портрет клиента. Это позволит понять ценности человека, определить, в какой среде ему будет комфортно, в какой — нет, какие техники и какая мотивация будут работать в его случае, а какие — окажутся бесполезными.
«Часто человек думает, что во всем может быть успешен — или, наоборот, что ни в чем не может быть успешен: зависит от степени самовлюбленности или самокритики, — поясняет Алексей Иванов. — Но на самом деле мы часто не понимаем, кто мы. Чтобы определить сильные и слабые стороны, я использую, например, Hogan Assessment — профессиональный тест, который используют американские компании при найме сотрудников».
Такие тесты интерпретируют специалисты: они смотрят не только на самоидентификацию (как мы видим себя), но и на репутацию (как нас видят другие).
Чтобы создать собственный психологический портрет самому, можно самостоятельно использовать упрощенные тесты. Из них Алексей Иванов рекомендует.
- Hexaco оценивает личность по шести параметрам: открытости опыту, добросовестности, экстраверсии, доброжелательности, нейротизму, честности. Hexaco разработали ученые из канадского Университета Калгари на основе другого популярного опросника — «Большой пятерки» для диагностики личностных факторов темперамента и характера. Тест специализированный, поэтому придется долго разбираться с результатами.
- StrengthsFinder 2.0 определяет ваши сильные и слабые стороны. Его разработал психолог Дон Клифтон, чтобы раскрывать природные таланты, выяснять, что мы делаем лучше всего — и сосредотачиваться на развитии именно этих направлений. Научная база теста скромнее, чем у Hogan и Hexaco, зато не придется мучиться с интерпретацией.
- MBTI определяет личность по типологии Майерс-Бриггс, в основе — архетипы Юнга (в России известен в варианте соционики). Тип присваивается в зависимости от того, какие параметры вам более свойственны: интроверсия или экстраверсия, интуиция или восприятие, чувство или мышление, оценка или восприятие. MBTI — популярный тест, но он не считается научным. Зато интуитивно понятен и не требует специальных знаний для интерпретации.
Мы также советуем следующие тесты с достаточной научной базой: MMPI, опросник Леонгарда-Шмишека, Via Character, Big Five и The TPAQ-45 Complete Personality Profile.
Узнайте больше- Tiny Habits: The Small Changes That Change Everything
Профессор поведенческой психологии Стэнфорда Би Джей Фогг делит поведение на три компонента: мотивацию, способность и триггеры. Если хотя бы один из них отсутствует, смена привычки или закрепление новой не происходит. С помощью модели Фогга можно понять, чего именно не хватает для желаемого поведения, — и работать с проблемным компонентом.
- Hooked: How to Build Habit-Forming Products
Израильский инвестор Нир Эйал рассказывает о модели «петли» (Hook Model) и объясняет, как сформировать привычку через четырехэтапную последовательность.
- Thinking, Fast and Slow («Думай медленно… решай быстро»)
Дэниел Канеман, лауреат Нобелевской премии по экономике 2002 года за исследования поведения в условиях неопределенности, объясняет, как механизмы мышления заставляют нас действовать иррационально.
Понравился материал? Подпишитесь на еженедельную email-рассылку Reminder!Наблюдатель
/ Шаблоны проектирования / Модели поведения
Также известен как: Event-Subscriber, Listener
IntentObserver — это поведенческий шаблон проектирования, который позволяет вам определить механизм подписки для уведомления нескольких объектов о любых событиях, происходящих с объектом, за которым они наблюдают.
Проблема Представьте, что у вас есть два типа объектов: Клиент
и Магазин
. Покупатель очень заинтересован в конкретной марке товара (скажем, это новая модель iPhone), которая должна появиться в магазине в самое ближайшее время.
Покупатель мог каждый день посещать магазин и проверять наличие товара. Но пока продукт все еще в пути, большинство этих поездок было бы бессмысленным.
Посещение магазина и рассылка спама
С другой стороны, магазин может рассылать тонны электронных писем (которые могут считаться спамом) всем покупателям каждый раз, когда появляется новый продукт. Это избавило бы некоторых покупателей от бесконечных походов в магазин. В то же время это расстроит других клиентов, которые не заинтересованы в новых продуктах.
Похоже, у нас конфликт. Либо покупатель тратит время на проверку наличия товара, либо магазин тратит ресурсы, уведомляя не тех покупателей.
РешениеОбъект с интересным состоянием часто называют субъект , но поскольку он также будет уведомлять другие объекты об изменениях своего состояния, мы назовем его издатель . Все остальные объекты, которые хотят отслеживать изменения в состоянии издателя, называются подписчиков .
Шаблон Observer предлагает добавить механизм подписки в класс издателя, чтобы отдельные объекты могли подписаться или отказаться от подписки на поток событий, поступающих от этого издателя. Не бойся! Все не так сложно, как кажется. На самом деле этот механизм состоит из 1) поля массива для хранения списка ссылок на объекты-подписчики и 2) нескольких общедоступных методов, которые позволяют добавлять подписчиков в этот список и удалять их из него.
Механизм подписки позволяет отдельным объектам подписываться на уведомления о событиях.
Теперь всякий раз, когда с издателем происходит важное событие, он проходит через своих подписчиков и вызывает специальный метод уведомления для их объектов.
Реальные приложения могут иметь десятки различных классов подписчиков, заинтересованных в отслеживании событий одного и того же класса издателя. Вы бы не хотели связывать издателя со всеми этими классами. Кроме того, вы можете даже не знать о некоторых из них заранее, если ваш класс издателя предполагается использовать другими людьми.
Вот почему так важно, чтобы все подписчики реализовывали один и тот же интерфейс и чтобы издатель общался с ними только через этот интерфейс. Этот интерфейс должен объявить метод уведомления вместе с набором параметров, которые издатель может использовать для передачи некоторых контекстных данных вместе с уведомлением.
Publisher уведомляет подписчиков, вызывая определенный метод уведомления для их объектов.
Если в вашем приложении есть несколько разных типов издателей и вы хотите, чтобы ваши подписчики были совместимы со всеми из них, вы можете пойти еще дальше и заставить всех издателей использовать один и тот же интерфейс. Этот интерфейс должен был бы описать только несколько методов подписки. Интерфейс позволит подписчикам наблюдать за состояниями издателей без привязки к их конкретным классам.
Реальная аналогияПодписка на журналы и газеты.
Если вы подписаны на газету или журнал, вам больше не нужно идти в магазин, чтобы проверить, есть ли в наличии следующий выпуск. Вместо этого издатель отправляет новые выпуски прямо в ваш почтовый ящик сразу после публикации или даже заранее.
Издатель ведет список подписчиков и знает, какие журналы им интересны. Подписчики могут выйти из списка в любое время, когда они хотят, чтобы издатель не присылал им новые номера журналов.
СтруктураPublisher выдает события, представляющие интерес для других объектов. Эти события происходят, когда издатель изменяет свое состояние или выполняет некоторые действия. Издатели содержат инфраструктуру подписки, которая позволяет новым подписчикам присоединяться, а текущим подписчикам выходить из списка.
Когда происходит новое событие, издатель просматривает список подписки и вызывает метод уведомления, объявленный в интерфейсе подписчика для каждого объекта подписчика.
Интерфейс подписчика объявляет интерфейс уведомлений. В большинстве случаев он состоит из одного метода
update
. Метод может иметь несколько параметров, которые позволяют издателю передавать некоторые сведения о событии вместе с обновлением.Конкретные подписчики выполняют некоторые действия в ответ на уведомления, выпущенные издателем. Все эти классы должны реализовывать один и тот же интерфейс, чтобы издатель не был связан с конкретными классами.
Обычно подписчикам нужна некоторая контекстная информация для правильной обработки обновления. По этой причине издатели часто передают некоторые данные контекста в качестве аргументов метода уведомления. Издатель может передать себя в качестве аргумента, позволяя подписчику получать любые необходимые данные напрямую.
Клиент создает объекты издателя и подписчика отдельно, а затем регистрирует подписчиков для обновлений издателя.
В этом примере шаблон Observer позволяет объекту текстового редактора уведомлять другие объекты службы об изменении своего состояния.
Уведомление объектов о событиях, происходящих с другими объектами.
Список подписчиков составляется динамически: объекты могут начинать или прекращать прослушивание уведомлений во время выполнения, в зависимости от желаемого поведения вашего приложения.
В этой реализации класс редактора не поддерживает список подписок сам по себе. Он делегирует эту работу специальному вспомогательному объекту, предназначенному именно для этого. Вы можете обновить этот объект, чтобы он служил централизованным диспетчером событий, позволяя любому объекту выступать в роли издателя.
Добавление новых подписчиков в программу не требует внесения изменений в существующие классы издателей, если они работают со всеми подписчиками через один и тот же интерфейс.
// Базовый класс издателя включает управление подпиской
// код и методы уведомления.
класс EventManager
прослушиватели частных полей: хеш-карта типов событий и прослушивателей
метод подписки (тип события, слушатель)
listeners.add (тип события, слушатель)
метод отказа от подписки (тип события, слушатель)
listeners. remove (тип события, слушатель)
метод уведомления (тип события, данные)
foreach (слушатель в listeners.of(eventType)) делать
слушатель.обновление(данные)
// Конкретный издатель содержит реальную бизнес-логику,
// интересно некоторым подписчикам. Мы могли бы вывести этот класс
// от базового издателя, но это не всегда возможно в
// реальная жизнь, потому что конкретный издатель уже может быть
// подкласс. В этом случае можно пропатчить логику подписки
// вместе с композицией, как мы сделали здесь.
Редактор классов
события общедоступного поля: EventManager
файл закрытого поля: File
конструктор Редактор() есть
события = новый менеджер событий()
// Методы бизнес-логики могут оповещать подписчиков о
// изменения.
метод openFile (путь)
this.file = новый файл (путь)
events.notify("открыть", файл.имя)
метод saveFile() есть
файл.записать()
events.notify("сохранить", имя_файла)
// ...
// Вот интерфейс подписчика. Если ваш язык программирования
// поддерживает функциональные типы, можно заменить целиком
// иерархия абонентов с набором функций.
интерфейс EventListener
обновление метода (имя файла)
// Конкретные подписчики реагируют на обновления, выпущенные издателем
// они прикреплены к.
класс LoggingListener реализует EventListener
Журнал закрытого поля: Файл
сообщение частного поля: строка
конструктор LoggingListener (log_filename, сообщение)
this.log = новый файл (log_filename)
это.сообщение = сообщение
обновление метода (имя файла)
log.write(заменить('%s',имя файла,сообщение))
класс EmailAlertsListener реализует EventListener
электронная почта частного поля: строка
сообщение частного поля: строка
конструктор EmailAlertsListener (электронная почта, сообщение)
this.email = электронная почта
это.сообщение = сообщение
обновление метода (имя файла)
system.email(электронная почта, заменить('%s',имя файла,сообщение))
// Приложение может настроить издателей и подписчиков на
// время выполнения.
приложение класса
метод config() есть
редактор = новый редактор()
logger = новый LoggingListener(
"/путь/к/log.txt",
"Кто-то открыл файл: %s")
editor.events.subscribe("открыть", регистратор)
emailAlerts = новый EmailAlertsListener(
"[email protected]",
"Кто-то изменил файл: %s")
editor.events.subscribe("сохранить", emailAlerts)
ПрименимостьИспользуйте шаблон Наблюдатель, когда изменение состояния одного объекта может потребовать изменения других объектов, а фактический набор объектов заранее неизвестен или изменяется динамически.
С этой проблемой часто можно столкнуться при работе с классами графического пользовательского интерфейса. Например, вы создали пользовательские классы кнопок и хотите, чтобы клиенты подключали к вашим кнопкам некоторый пользовательский код, чтобы он срабатывал всякий раз, когда пользователь нажимает кнопку.
Шаблон Observer позволяет любому объекту, реализующему интерфейс подписчика, подписываться на уведомления о событиях в объектах-издателях. Вы можете добавить механизм подписки к своим кнопкам, позволяя клиентам подключать свой пользовательский код через настраиваемые классы подписчиков.
Используйте шаблон, когда некоторые объекты в вашем приложении должны наблюдать за другими, но только в течение ограниченного времени или в определенных случаях.
Список подписки является динамическим, поэтому подписчики могут присоединяться к списку или выходить из него в любое время.
Как реализоватьПросмотрите свою бизнес-логику и попытайтесь разбить ее на две части: основная функциональность, независимая от другого кода, будет действовать как издатель; остальное превратится в набор классов подписчиков.
Объявить абонентский интерфейс. Как минимум, он должен объявить один метод
update
.Объявить интерфейс публикатора и описать пару методов для добавления объекта-подписчика в список и его удаления из списка. Помните, что веб-мастера должны работать с подписчиками только через интерфейс подписчика.
Решите, где разместить фактический список подписки и реализацию методов подписки. Обычно этот код выглядит одинаково для всех типов издателей, поэтому очевидное место для его размещения — абстрактный класс, полученный непосредственно из интерфейса издателя. Конкретные издатели расширяют этот класс, наследуя поведение подписки.
Однако, если вы применяете шаблон к существующей иерархии классов, рассмотрите подход, основанный на композиции: поместите логику подписки в отдельный объект и заставьте ее использовать всех реальных издателей.
Создайте конкретные классы издателей. Каждый раз, когда что-то важное происходит внутри издателя, он должен уведомлять всех своих подписчиков.
Реализовать методы уведомления об обновлении в конкретных классах подписчиков. Большинству подписчиков потребуются некоторые контекстные данные о событии. Его можно передать в качестве аргумента метода уведомления.
Но есть и другой вариант. Получив уведомление, подписчик может получить любые данные непосредственно из уведомления. В этом случае издатель должен передать себя через метод обновления. Менее гибкий вариант — постоянно связывать издателя с подписчиком через конструктор.
Клиент должен создать всех необходимых подписчиков и зарегистрировать их у соответствующих издателей.
- Открытый/Закрытый принцип . Вы можете вводить новые классы подписчиков, не изменяя код издателя (и наоборот, если есть интерфейс издателя).
- Вы можете установить отношения между объектами во время выполнения.
- Подписчики уведомляются в случайном порядке.
Chain of Responsibility, Command, Mediator и Observer обращаются к различным способам соединения отправителей и получателей запросов:
- Цепочка ответственности передает запрос последовательно по динамической цепочке потенциальных получателей, пока один из них не обработает его.
- Команда устанавливает однонаправленные соединения между отправителями и получателями.
- Посредник устраняет прямые соединения между отправителями и получателями, заставляя их взаимодействовать косвенно через объект-посредник.
- Observer позволяет получателям динамически подписываться на получение запросов и отписываться от них.
Разница между посредником и наблюдателем часто неуловима. В большинстве случаев вы можете реализовать любой из этих шаблонов; но иногда вы можете применить оба одновременно. Давайте посмотрим, как мы можем это сделать.
Основной целью Mediator является устранение взаимных зависимостей между набором компонентов системы. Вместо этого эти компоненты становятся зависимыми от одного объекта-посредника. Целью Observer является установление динамических односторонних связей между объектами, где одни объекты действуют как подчиненные другим.
Существует популярная реализация шаблона Mediator , основанная на Observer . Объект посредника играет роль издателя, а компоненты действуют как подписчики, которые подписываются на события посредника и отписываются от них. Когда Посредник реализован таким образом, он может выглядеть очень похоже на Наблюдатель .
Когда вы запутались, помните, что вы можете реализовать паттерн «Посредник» другими способами. Например, вы можете навсегда связать все компоненты с одним и тем же объектом-посредником. Эта реализация не будет похожа на Observer , но по-прежнему будет экземпляром шаблона посредника.
Теперь представьте себе программу, в которой все компоненты стали издателями, позволяющими динамически соединяться друг с другом. Не будет централизованного объекта-посредника, только распределенный набор наблюдателей.
Команда
/ Шаблоны проектирования / Модели поведения
Также известен как: Действие, Транзакция
IntentКоманда — это поведенческий шаблон проектирования, который превращает запрос в автономный объект, содержащий всю информацию о запросе. Это преобразование позволяет передавать запросы в качестве аргументов метода, откладывать выполнение запроса или ставить его в очередь, а также поддерживать операции, которые нельзя отменить.
Проблема Представьте, что вы работаете над новым текстовым редактором. Ваша текущая задача — создать панель инструментов с кучей кнопок для различных операций редактора. Вы создали очень аккуратный класс Button
, который можно использовать для кнопок на панели инструментов, а также для общих кнопок в различных диалогах.
Все кнопки приложения являются производными от одного класса.
Хотя все эти кнопки выглядят одинаково, все они должны выполнять разные функции. Куда бы вы поместили код для различных обработчиков кликов этих кнопок? Самое простое решение — создать множество подклассов для каждого места, где используется кнопка. Эти подклассы будут содержать код, который должен выполняться по нажатию кнопки.
Множество подклассов кнопок. Что может пойти не так?
Вскоре вы понимаете, что этот подход глубоко ошибочен. Во-первых, у вас есть огромное количество подклассов, и это было бы нормально, если бы вы не рисковали сломать код в этих подклассах каждый раз, когда вы модифицируете базовый класс Button
. Проще говоря, ваш код GUI стал неловко зависеть от изменчивого кода бизнес-логики.
Несколько классов реализуют одну и ту же функциональность.
А вот и самое безобразное. Некоторые операции, такие как копирование/вставка текста, необходимо будет вызывать из нескольких мест. Например, пользователь может нажать маленькую кнопку «Копировать» на панели инструментов, скопировать что-то через контекстное меню или просто нажать 9.0018 Ctrl+C на клавиатуре.
Изначально, когда в нашем приложении была только панель инструментов, можно было поместить реализацию различных операций в подклассы кнопок. Другими словами, иметь код для копирования текста внутри подкласса CopyButton
было нормально. Но тогда при реализации контекстных меню, ярлыков и прочего приходится либо дублировать код операции во многих классах, либо делать меню зависимыми от кнопок, что еще хуже.
Хороший дизайн программного обеспечения часто основан на принципе разделения задач , что обычно приводит к разбиению приложения на слои. Самый распространенный пример: слой для графического пользовательского интерфейса и еще один слой для бизнес-логики. Слой GUI отвечает за рендеринг красивой картинки на экране, захват любого ввода и отображение результатов действий пользователя и приложения. Однако, когда дело доходит до чего-то важного, например расчета траектории движения Луны или составления годового отчета, уровень графического интерфейса делегирует работу нижележащему уровню бизнес-логики.
В коде это может выглядеть так: объект GUI вызывает метод объекта бизнес-логики, передавая ему некоторые аргументы. Этот процесс обычно описывается как отправка одним объектом другому запроса .
Объекты GUI могут напрямую обращаться к объектам бизнес-логики.
Шаблон Command предполагает, что объекты GUI не должны отправлять эти запросы напрямую. Вместо этого вы должны извлечь все детали запроса, такие как вызываемый объект, имя метода и список аргументов, в отдельный класс команды с одним методом, который запускает этот запрос.
Командные объекты служат связующим звеном между различными объектами графического интерфейса и бизнес-логики. Отныне объекту GUI не нужно знать, какой объект бизнес-логики получит запрос и как он будет обработан. Объект GUI просто запускает команду, которая обрабатывает все детали.
Доступ к уровню бизнес-логики с помощью команды.
Следующим шагом является реализация команд с одинаковым интерфейсом. Обычно он имеет только один метод выполнения, который не принимает параметров. Этот интерфейс позволяет использовать различные команды с одним и тем же отправителем запроса, не связывая его с конкретными классами команд. В качестве бонуса теперь вы можете переключать объекты команд, связанные с отправителем, эффективно изменяя поведение отправителя во время выполнения.
Возможно, вы заметили недостающую часть головоломки — параметры запроса. Объект GUI мог предоставить объекту бизнес-уровня некоторые параметры. Поскольку метод выполнения команды не имеет никаких параметров, как мы можем передать детали запроса получателю? Оказывается, команда должна быть либо предварительно сконфигурирована с этими данными, либо способна получить их самостоятельно.
Объекты графического интерфейса делегируют работу командам.
Вернемся к нашему текстовому редактору. После применения паттерна Command нам больше не нужны все эти подклассы кнопок для реализации различных действий при нажатии. Достаточно поместить одно поле в основание Button
Класс, который хранит ссылку на объект команды и заставляет кнопку выполнять эту команду по щелчку.
Вы создадите набор классов команд для каждой возможной операции и свяжете их с определенными кнопками в зависимости от предполагаемого поведения кнопок.
Другие элементы графического интерфейса, такие как меню, ярлыки или целые диалоги, могут быть реализованы таким же образом. Они будут связаны с командой, которая выполняется, когда пользователь взаимодействует с элементом GUI. Как вы уже, наверное, догадались, элементы, относящиеся к одним и тем же операциям, будут связаны с одними и теми же командами, что предотвратит дублирование кода.
В результате команды становятся удобным промежуточным уровнем, уменьшающим связь между графическим интерфейсом и уровнями бизнес-логики. И это лишь малая часть преимуществ, которые может предложить шаблон Command!
Аналогия из реального мираОформление заказа в ресторане.
После долгой прогулки по городу вы попадаете в хороший ресторан и садитесь за столик у окна. К вам подходит приветливый официант и быстро принимает ваш заказ, записывая его на листе бумаги. Официант идет на кухню и вешает заказ на стену. Через некоторое время заказ попадает к повару, который его читает и соответственно готовит. Повар кладет еду на поднос вместе с заказом. Официант обнаруживает поднос, проверяет заказ, чтобы убедиться, что все так, как вы хотели, и приносит все к вашему столику.
Бумажный заказ служит командой. Он остается в очереди до тех пор, пока шеф-повар не будет готов его подать. Заказ содержит всю необходимую информацию, необходимую для приготовления блюда. Это позволяет шеф-повару сразу приступить к приготовлению, а не бегать, уточняя детали заказа у вас напрямую.
СтруктураКласс Sender (также известный как Invoker ) отвечает за инициирование запросов. Этот класс должен иметь поле для хранения ссылки на объект команды. Отправитель запускает эту команду вместо отправки запроса непосредственно получателю. Обратите внимание, что отправитель не несет ответственности за создание объекта команды. Обычно он получает предварительно созданную команду от клиента через конструктор.
Интерфейс Command обычно объявляет только один метод для выполнения команды.
Конкретные команды реализуют различные виды запросов. Конкретная команда не должна выполнять работу сама по себе, а должна передавать вызов одному из объектов бизнес-логики. Однако ради упрощения кода эти классы можно объединить.
Параметры, необходимые для выполнения метода на принимающем объекте, могут быть объявлены как поля в конкретной команде. Вы можете сделать объекты команд неизменяемыми, разрешив инициализацию этих полей только через конструктор.
Класс Receiver содержит некоторую бизнес-логику. В качестве приемника может выступать практически любой объект. Большинство команд обрабатывают только детали того, как запрос передается получателю, в то время как сам получатель выполняет фактическую работу.
Клиент создает и настраивает конкретные объекты команд. Клиент должен передать все параметры запроса, включая экземпляр получателя, в конструктор команды. После этого результирующая команда может быть связана с одним или несколькими отправителями.
В этом примере шаблон Команда помогает отслеживать историю выполненных операций и позволяет при необходимости отменить операцию.
Невыполнимые операции в текстовом редакторе.
Команды, которые приводят к изменению состояния редактора (например, вырезание и вставка), создают резервную копию состояния редактора перед выполнением операции, связанной с командой. После выполнения команды она помещается в историю команд (стек объектов команд) вместе с резервной копией состояния редактора на тот момент. Позже, если пользователю потребуется отменить операцию, приложение может взять самую последнюю команду из истории, прочитать связанную резервную копию состояния редактора и восстановить ее.
Код клиента (элементы графического интерфейса, история команд и т. д.) не привязан к конкретным классам команд, поскольку работает с командами через командный интерфейс. Такой подход позволяет вводить в приложение новые команды, не нарушая существующий код.
// Базовый класс команд определяет общий интерфейс для всех
// конкретные команды.
команда абстрактного класса
приложение защищенного поля: приложение
редактор защищенного поля: Редактор
резервная копия защищенного поля: текст
команда конструктора (приложение: приложение, редактор: редактор)
это.приложение = приложение
this.editor = редактор
// Сделать резервную копию состояния редактора.
метод saveBackup()
резервная копия = редактор.текст
// Восстановить состояние редактора.
метод отмены() есть
редактор.текст = резервная копия
// Метод выполнения объявляется абстрактным, чтобы заставить все
// конкретные команды для предоставления собственных реализаций.
// Метод должен возвращать true или false в зависимости от того,
// команда изменяет состояние редактора.
абстрактный метод выполнить()
// Здесь идут конкретные команды.
класс CopyCommand расширяет команду
// Команда копирования не сохраняется в истории, т.к.
// не изменяет состояние редактора.
метод выполнить ()
app.clipboard = редактор.getSelection()
вернуть ложь
класс CutCommand расширяет команду
// Команда cut изменяет состояние редактора, поэтому
// его нужно сохранить в историю. И он будет сохранен как
// пока метод возвращает true.
метод выполнить ()
сохранить резервную копию ()
app.clipboard = редактор.getSelection()
редактор.deleteSelection()
вернуть истину
класс PasteCommand расширяет команду
метод выполнить ()
сохранить резервную копию ()
editor.replaceSelection(app.clipboard)
вернуть истину
// Операция отмены также является командой.
класс UndoCommand расширяет команду
метод выполнить ()
приложение.отменить()
вернуть ложь
// Глобальная история команд — это просто стек.
класс CommandHistory
история частных полей: массив команд
// Последний в. ..
метод push(c: Command)
// Поместите команду в конец массива истории.
// ...первым вышел
метод pop(): команда
// Получить самую последнюю команду из истории.
// Класс редактора имеет фактические операции редактирования текста. Играет
// роль получателя: все команды заканчиваются делегированием
// выполнение методов редактора.
Редактор классов
текст поля: строка
метод getSelection() есть
// Вернуть выделенный текст.
метод deleteSelection() есть
// Удалить выделенный текст.
метод replaceSelection (текст) есть
// Вставляем содержимое буфера обмена в текущую
// позиция.
// Класс приложения устанавливает объектные отношения. Он действует как
// отправитель: когда что-то нужно сделать, он создает команду
// объект и выполняет его.
приложение класса
поле буфера обмена: строка
field editors: массив редакторов
поле activeEditor: Редактор
история поля: CommandHistory
// Код, который назначает команды объектам пользовательского интерфейса, может выглядеть
// так.
метод createUI() есть
// ...
копировать = функция() {выполнитькоманду(
новая команда копирования (это, активный редактор)) }
copyButton.setCommand(копировать)
ярлыки.onKeyPress("Ctrl+C", копировать)
вырезать = функция () { выполнить команду (
новая CutCommand (это, активный редактор)) }
cutButton.setCommand(вырезать)
ярлыки.onKeyPress("Ctrl+X", вырезать)
вставить = функция() {выполнитькоманду(
новая команда PasteCommand (это, активный редактор)) }
pasteButton.setCommand(вставить)
ярлыки.onKeyPress("Ctrl+V", вставить)
отменить = функция () { выполнить команду (
новая UndoCommand (это, активный редактор)) }
undoButton.setCommand(отменить)
ярлыки.onKeyPress("Ctrl+Z", отменить)
// Выполняем команду и проверяем, нужно ли ее добавить в
// история.
метод executeCommand (команда)
если (команда.выполнить)
history. push(команда)
// Возьмем самую последнюю команду из истории и запустим ее
// метод отмены. Обратите внимание, что мы не знаем класс этого
// команда. Но нам и не надо, так как командование знает
// как отменить собственное действие.
метод отмены() есть
команда = история.поп()
если (команда != ноль)
команда.отменить()
ПрименимостьИспользуйте шаблон Command, если вы хотите параметризовать объекты с помощью операций.
Шаблон Command может превратить вызов определенного метода в автономный объект. Это изменение открывает много интересных применений: вы можете передавать команды в качестве аргументов метода, хранить их внутри других объектов, переключать связанные команды во время выполнения и т. д.
Вот пример: вы разрабатываете компонент графического интерфейса, такой как контекстное меню. , и вы хотите, чтобы ваши пользователи могли настраивать элементы меню, которые запускают операции, когда конечный пользователь щелкает элемент.
Используйте шаблон Command, если вы хотите поставить операции в очередь, запланировать их выполнение или выполнить их удаленно.
Как и любой другой объект, команду можно сериализовать, что означает преобразование ее в строку, которую можно легко записать в файл или базу данных. Позже строка может быть восстановлена как исходный объект команды. Таким образом, вы можете откладывать и планировать выполнение команды. Но это еще не все! Точно так же вы можете ставить в очередь, регистрировать или отправлять команды по сети.
Используйте шаблон Command, если вы хотите реализовать обратимые операции.
Хотя существует множество способов реализации отмены/возврата, шаблон Command, пожалуй, самый популярный из всех.
Для возможности возврата операций необходимо реализовать историю выполненных операций. История команд — это стек, который содержит все выполненные объекты команд вместе с соответствующими резервными копиями состояния приложения.
Этот метод имеет два недостатка. Во-первых, не так просто сохранить состояние приложения, потому что некоторые из них могут быть приватными. Эту проблему можно решить с помощью шаблона Memento.
Во-вторых, резервные копии состояния могут потреблять довольно много оперативной памяти. Поэтому иногда можно прибегнуть к альтернативной реализации: вместо восстановления прошлого состояния команда выполняет обратную операцию. Обратная операция тоже имеет свою цену: она может оказаться сложной или даже невозможной для реализации.
Как реализоватьОбъявите командный интерфейс с единым методом выполнения.
Начать извлекать запросы в конкретные классы команд, которые реализуют командный интерфейс. Каждый класс должен иметь набор полей для хранения аргументов запроса вместе со ссылкой на фактический объект-получатель. Все эти значения должны быть инициализированы через конструктор команды.
Определите классы, которые будут действовать как отправители . Добавьте в эти классы поля для хранения команд. Отправители должны общаться со своими командами только через командный интерфейс. Отправители обычно не создают объекты команд самостоятельно, а получают их из клиентского кода.
Измените отправителей, чтобы они выполняли команду вместо прямой отправки запроса получателю.
Клиент должен инициализировать объекты в следующем порядке:
- Создание получателей.
- Создайте команды и при необходимости свяжите их с приемниками.
- Создайте отправителей и свяжите их с определенными командами.
- Принцип единой ответственности . Вы можете отделить классы, которые вызывают операции от классов, которые выполняют эти операции.
- Открытый/Закрытый принцип . Вы можете вводить в приложение новые команды, не нарушая существующий клиентский код.
- Вы можете реализовать отмену/возврат.
- Можно реализовать отложенное выполнение операций.
- Вы можете собрать набор простых команд в сложный.
- Код может стать более сложным, так как вы вводите совершенно новый слой между отправителями и получателями.
Chain of Responsibility, Command, Mediator и Observer обращаются к различным способам соединения отправителей и получателей запросов:
- Цепочка ответственности передает запрос последовательно по динамической цепочке потенциальных получателей, пока один из них не обработает его.
- Команда устанавливает однонаправленные соединения между отправителями и получателями.
- Посредник устраняет прямые соединения между отправителями и получателями, заставляя их взаимодействовать косвенно через объект-посредник.
- Observer позволяет получателям динамически подписываться на получение запросов и отписываться от них.
Обработчики в цепочке ответственности могут быть реализованы как команды. В этом случае вы можете выполнять множество различных операций над одним и тем же объектом контекста, представленным запросом.
Однако есть и другой подход, когда сам запрос является объектом Command . В этом случае вы можете выполнять одну и ту же операцию в ряде различных контекстов, связанных в цепочку.
Вы можете использовать Command и Memento вместе при реализации «отмены». В этом случае команды отвечают за выполнение различных операций над целевым объектом, а сувениры сохраняют состояние этого объекта непосредственно перед выполнением команды.
Command и Strategy могут выглядеть одинаково, потому что вы можете использовать их для параметризации объекта с помощью некоторого действия. Однако у них очень разные намерения.