Цепочка обязанностей на php
Содержание:
- Псевдокод
- Псевдокод
- Стратегия (Strategy)
- Проблема
- Заместитель (Proxy)
- Знакомство с редактором композиции Playlist
- Минусы
- Проблема
- Рішення
- Решение
- Фасад (Facade)
- Концептуальный пример
- Идея паттерна Шаблонный метод (Template method)
- Проблема
- Проблема
- Решение
- Проблема
- Посредник (Mediator)
- Псевдокод
- Приспособленец (Flyweight)
- Команда (Command)
- Проблема
- Плюсы
- Концептуальный пример
Псевдокод
В этом примере Легковес помогает сэкономить оперативную память при отрисовке на экране миллионов объектов-деревьев.
Легковес выделяет повторяющуюся часть состояния из основного класса и помещает его в дополнительный класс .
Теперь, вместо хранения повторяющихся данных во всех объектах, отдельные деревья будут ссылаться на несколько общих объектов, хранящих эти данные. Клиент работает с деревьями через фабрику деревьев, которая скрывает от него сложность кеширования общих данных деревьев.
Таким образом, программа будет использовать намного меньше оперативной памяти, что позволит отрисовать больше деревьев на экране на том же железе.
Псевдокод
В этом примере Фабричный метод помогает создавать кросс-платформенные элементы интерфейса, не привязывая основной код программы к конкретным классам элементов.
Пример кросс-платформенного диалога.
Фабричный метод объявлен в классе диалогов. Его подклассы относятся к различным операционным системам. Благодаря фабричному методу, вам не нужно переписывать логику диалогов под каждую систему. Подклассы могут наследовать почти весь код из базового диалога, изменяя типы кнопок и других элементов, из которых базовый код строит окна графического пользовательского интерфейса.
Базовый класс диалогов работает с кнопками через их общий программный интерфейс. Поэтому, какую вариацию кнопок ни вернул бы фабричный метод, диалог останется рабочим. Базовый класс не зависит от конкретных классов кнопок, оставляя подклассам решение о том, какой тип кнопок создавать.
Такой подход можно применить и для создания других элементов интерфейса. Хотя каждый новый тип элементов будет приближать вас к Абстрактной фабрике.
Стратегия (Strategy)
Википедия гласит:
Пример из жизни: Возьмём пример с пузырьковой сортировкой. Мы её реализовали, но с ростом объёмов данных сортировка работа стала выполняться очень медленно. Тогда мы сделали быструю сортировку. Алгоритм работает быстрее на больших объёмах, но на маленьких он очень медленный. Тогда мы реализовали стратегию, при которой для маленьких объёмов данных используется пузырьковая сортировка, а для больших объёмов — быстрая.
Простыми словами: Шаблон стратегия позволяет переключаться между алгоритмами или стратегиями в зависимости от ситуации.
Перейдем к коду. Возьмем наш пример. Изначально у нас есть наша и разные её реализации:
И у нас есть , который собирается использовать какую-то стратегию:
Пример использования:
Примеры на Java и Python.
Проблема
Предположим, что вы пишете программу текстового редактора. Помимо обычного редактирования, ваш редактор позволяет менять форматирование текста, вставлять картинки и прочее.
В какой-то момент вы решили сделать все эти действия отменяемыми. Для этого вам нужно сохранять текущее состояние редактора перед тем, как выполнить любое действие. Если потом пользователь решит отменить своё действие, вы достанете копию состояния из истории и восстановите старое состояние редактора.
Перед выполнением команды вы можете сохранить копию состояния редактора, чтобы потом иметь возможность отменить операцию.
Чтобы сделать копию состояния объекта, достаточно скопировать значение его полей. Таким образом, если вы сделали класс редактора достаточно открытым, то любой другой класс сможет заглянуть внутрь, чтобы скопировать его состояние.
Казалось бы, что ещё нужно? Ведь теперь любая операция сможет сделать резервную копию редактора перед своим действием. Но такой наивный подход обеспечит вам уйму проблем в будущем. Ведь если вы решите провести рефакторинг — убрать или добавить парочку полей в класс редактора — то придётся менять код всех классов, которые могли копировать состояние редактора.
Как команде создать снимок состояния редактора, если все его поля приватные?
Но это ещё не все. Давайте теперь рассмотрим сами копии состояния редактора. Из чего состоит состояние редактора? Даже самый примитивный редактор должен иметь несколько полей для хранения текущего текста, позиции курсора и прокрутки экрана. Чтобы сделать копию состояния, вам нужно записать значения всех этих полей в некий «контейнер».
Скорее всего, вам понадобится хранить массу таких контейнеров в качестве истории операций, поэтому удобнее всего сделать их объектами одного класса. Этот класс должен иметь много полей, но практически никаких методов. Чтобы другие объекты могли записывать и читать из него данные, вам придётся сделать его поля публичными. Но это приведёт к той же проблеме, что и с открытым классом редактора. Другие классы станут зависимыми от любых изменений в классе контейнера, который подвержен тем же изменениям, что и класс редактора.
Заместитель (Proxy)
Википедия гласит:
Пример из жизни: Вы когда-нибудь использовали карту доступа, чтобы пройти через дверь? Есть несколько способов открыть дверь: например, она может быть открыта при помощи карты доступа или нажатия кнопки, которая обходит защиту. Основная функциональность двери — это открытие, но заместитель, добавленный поверх этого, добавляет функциональность. Но лучше я объясню это на примере кода чуть ниже.
Простыми словами: Используя шаблон заместитель, класс отображает функциональность другого класса.
Перейдем к коду. Возьмем наш пример с безопасностью. Сначала у нас есть интерфейс и его реализация:
Затем у нас есть заместитель для защиты любых наших дверей:
Пример использования:
Другим примером будет реализация маппинга данных. Например, недавно я создал ODM (Object Data Mapper) для MongoDB, используя этот шаблон, где я написал заместитель вокруг классов mongo и использовал магический метод . Все вызовы методов были замещены оригинальным классом mongo, и полученный результат возвращался без изменений, но в случае или данные сопоставлялись необходимому классу, и возвращались в объект вместо .
Примеры на Java и Python.
Знакомство с редактором композиции Playlist
Итак, у нас есть два паттерна. Давайте попытаемся составить из них про-стейшую композицию.
Откроем окно редактора композиции Playlist (рис. 2.5, а) нажатием клавиши <F5> или кнопки (View playlist), расположенной на панели Shortcut.
Рис. 2.5 а
Переключим FL Studio в состояние редактирования композиции, для чего на транспортной панели (см. рис. 2.1, а) включим режим SONG. Если до этого был установлен режим воспроизведения, то после переключения в режим SONG воспроизведение прекратится — нечего воспроизводить, композиции пока не существует.
Сделаем так, чтобы первые четыре такта композиции звучал паттерн № 1(без малого барабана), а начиная с пятого такта — паттерн № 2 (с малым барабаном). Для этого воспользуемся инструментом Paint (кисточка), доступным в окне Playlist. Нарисуем линию из четырех квадратиков на уровне надписи Pattern 1 и ряд квадратиков на уровне надписи Pattern 2, как показано на рис. 2.5, б.
Рис. 2.5 б
Нажатием кнопки (Start) транспортной панели или клавиши <Про-бел> запустим композицию на воспроизведение и послушаем результат.
Точно такого же результата можно было бы добиться и другим способом.
Отредактируем существующие паттерны:
В паттерне № 1 присутствуют партии бочки и хэта, но отсутствует партия малого барабана (рис. 2.6, а);
В паттерне № 2 присутствует только партия малого барабана (рис. 2.6, б).
Перестроим нашу композицию так, как показано на рис. 2.7, а. В результате получится следующее: паттерн № 1 звучит сквозь всю композицию, а пат-терн № 2 вступает, начиная с четвертого такта, и воспроизводится параллельно с паттерном №1. Сравните с рис. 2.5, б, где паттерны 1 и 2 воспроизводятся последовательно. Второй способ более красив и нагляден, особенно если воспользоваться возможностью окна Playlist и присвоить трекам паттернов осмысленные названия.
Рис. 2.7 а
Щелкнем правой кнопкой мыши на надписи Pattern 1, возникнет малень-кое окошко Pattern 1 name (рис. 2.7, б). Введем новое название (имя) трека паттерна № 1 и нажмем клавишу <Enter>.
Рис. 2.7 б
Аналогичным способом переименуем трек паттерна № 2. Глядя на рис. 2.7, в, можно понять, когда в композиции звучат инструменты Kick (бочка) и HiHat (хэт), а когда вступает Snare (малый барабан).
Рис. 2.7 в
Суть приведенных примеров проста: одного и того же результата в FL Studio можно добиться разными способами
Кроме того, следует уделять внимание совместимости разных паттернов и избегать ситуации, когда в звучащих параллельно паттернах будут задействованы одни и те же генераторы
Минусы
Хотя легкое изменение кода под известный шаблон может упростить понимание кода, по мнению Стива Макконнелла, с применением шаблонов могут быть связаны две сложности. Во-первых, слепое следование некоторому выбранному шаблону может привести к усложнению программы. Во-вторых, у разработчика может возникнуть желание попробовать некоторый шаблон в деле без особых оснований.
Многие шаблоны проектирования в объектно-ориентированном проектировании можно рассматривать как идиоматическое воспроизведение элементов функциональных языков. Питер Норвиг утверждает, что 16 из 23 шаблонов, описанных в книге «Банды четырёх», в динамически-типизируемых языках реализуются существенно проще, чем в С++, либо оказываются незаметны. Пол Грэхэм считает саму идею шаблонов проектирования — антипаттерном, сигналом о том, что система не обладает достаточным уровнем абстракции, и необходима её тщательная переработка. Нетрудно видеть, что само определение шаблона как «готового решения, но не прямого обращения к библиотеке» по сути означает отказ от повторного использования в пользу дублирования. Это, очевидно, может быть неизбежным для сложных систем при использовании языков, не поддерживающих комбинаторы и полиморфизм типов, и это в принципе может быть исключено в языках, обладающих свойством гомоиконичности (хотя и не обязательно эффективно), так как любой шаблон может быть реализован в виде исполнимого кода.
Проблема
Представьте, что вы создаёте программу управления грузовыми перевозками. Сперва вы рассчитываете перевозить товары только на автомобилях. Поэтому весь ваш код работает с объектами класса .
В какой-то момент ваша программа становится настолько известной, что морские перевозчики выстраиваются в очередь и просят добавить поддержку морской логистики в программу.
Добавить новый класс не так-то просто, если весь код уже завязан на конкретные классы.
Отличные новости, правда?! Но как насчёт кода? Большая часть существующего кода жёстко привязана к классам . Чтобы добавить в программу классы морских , понадобится перелопатить всю программу. Более того, если вы потом решите добавить в программу ещё один вид транспорта, то всю эту работу придётся повторить.
Рішення
Патерн Шаблонний метод пропонує розбити алгоритм на послідовність кроків, описати ці кроки в окремих методах і викликати їх в одному шаблонному методі один за одним.
Це дозволить підкласам перевизначити деякі кроки алгоритму, залишаючи без змін його структуру та інші кроки, які для цього підкласу не є важливими.
У нашому прикладі з дата-майнінгом ми можемо створити загальний базовий клас для всіх трьох алгоритмів. Цей клас складатиметься з шаблонного методу, який послідовно викликає кроки розбору документів.
Шаблонний метод розбиває алгоритм на кроки, дозволяючи підкласами перевизначити деякі з них.
Для початку кроки шаблонного методу можна зробити абстрактними. З цієї причини усі підкласи повинні будуть реалізувати кожен з кроків по-своєму. В нашому випадку всі підкласи вже містять реалізацію кожного з кроків, тому додатково нічого робити не потрібно.
Справді важливим є наступний етап. Тепер ми можемо визначити спільну поведінку для всіх трьох класів і винести її до суперкласу. У нашому прикладі кроки відкривання та закривання документів відрізнятимуться для всіх підкласів, тому залишаться абстрактними. З іншого боку, код обробки даних, однаковий для всіх типів документів, переїде до базового класу.
Як бачите, у нас з’явилося два типа кроків: абстрактні, що кожен підклас обов’язково має реалізувати, а також кроки з типовою реалізацією, які можна перевизначити в підкласах, але це не обов’язково.
Решение
Паттерн Состояние предлагает создать отдельные классы для каждого состояния, в котором может пребывать объект, а затем вынести туда поведения, соответствующие этим состояниям.
Вместо того, чтобы хранить код всех состояний, первоначальный объект, называемый контекстом, будет содержать ссылку на один из объектов-состояний и делегировать ему работу, зависящую от состояния.
Документ делегирует работу своему активному объекту-состоянию.
Благодаря тому, что объекты состояний будут иметь общий интерфейс, контекст сможет делегировать работу состоянию, не привязываясь к его классу. Поведение контекста можно будет изменить в любой момент, подключив к нему другой объект-состояние.
Фасад (Facade)
Википедия гласит:
Пример из жизни: Как вы включаете компьютер? Нажимаю на кнопку включения, скажете вы. Это то, во что вы верите, потому что вы используете простой интерфейс, который компьютер предоставляет для доступа снаружи. Внутри же должно произойти гораздо больше вещей. Этот простой интерфейс для сложной подсистемы называется фасадом.
Простыми словами: Шаблон фасад предоставляет упрощенный интерфейс для сложной системы.
Перейдем к примерам в коде. Возьмем пример с компьютером. Изначально у нас есть класс :
Затем у нас есть фасад:
Пример использования:
Примеры на Java и Python.
Концептуальный пример
Этот пример показывает структуру паттерна Легковес, а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
После ознакомления со структурой, вам будет легче воспринимать второй пример, который рассматривает реальный случай использования паттерна в мире PHP.
index.php: Пример структуры паттерна
<?php namespace RefactoringGuru\Flyweight\Conceptual; /** * Легковес хранит общую часть состояния (также называемую внутренним * состоянием), которая принадлежит нескольким реальным бизнес-объектам. * Легковес принимает оставшуюся часть состояния (внешнее состояние, уникальное * для каждого объекта) через его параметры метода. */ class Flyweight { private $sharedState; public function __construct($sharedState) { $this->sharedState = $sharedState; } public function operation($uniqueState): void { $s = json_encode($this->sharedState); $u = json_encode($uniqueState); echo "Flyweight: Displaying shared ($s) and unique ($u) state.\n"; } } /** * Фабрика Легковесов создает объекты-Легковесы и управляет ими. Она * обеспечивает правильное разделение легковесов. Когда клиент запрашивает * легковес, фабрика либо возвращает существующий экземпляр, либо создает новый, * если он ещё не существует. */ class FlyweightFactory { /** * @var Flyweight[] */ private $flyweights = []; public function __construct(array $initialFlyweights) { foreach ($initialFlyweights as $state) { $this->flyweights = new Flyweight($state); } } /** * Возвращает хеш строки Легковеса для данного состояния. */ private function getKey(array $state): string { ksort($state); return implode("_", $state); } /** * Возвращает существующий Легковес с заданным состоянием или создает новый. */ public function getFlyweight(array $sharedState): Flyweight { $key = $this->getKey($sharedState); if (!isset($this->flyweights)) { echo "FlyweightFactory: Can't find a flyweight, creating new one.\n"; $this->flyweights = new Flyweight($sharedState); } else { echo "FlyweightFactory: Reusing existing flyweight.\n"; } return $this->flyweights; } public function listFlyweights(): void { $count = count($this->flyweights); echo "\nFlyweightFactory: I have $count flyweights:\n"; foreach ($this->flyweights as $key => $flyweight) { echo $key . "\n"; } } } /** * Клиентский код обычно создает кучу предварительно заполненных легковесов на * этапе инициализации приложения. */ $factory = new FlyweightFactory(, , , , , // ... ]); $factory->listFlyweights(); // ... function addCarToPoliceDatabase( FlyweightFactory $ff, $plates, $owner, $brand, $model, $color ) { echo "\nClient: Adding a car to database.\n"; $flyweight = $ff->getFlyweight(); // Клиентский код либо сохраняет, либо вычисляет внешнее состояние и // передает его методам легковеса. $flyweight->operation(); } addCarToPoliceDatabase($factory, "CL234IR", "James Doe", "BMW", "M5", "red", ); addCarToPoliceDatabase($factory, "CL234IR", "James Doe", "BMW", "X1", "red", ); $factory->listFlyweights();
Output.txt: Результат выполнения
Идея паттерна Шаблонный метод (Template method)
Паттерн проектирования — это продуманный способ построения исходного кода программы для решения часто возникающих в повседневном программировании проблем проектирования. Иными словами, это уже придуманное решения, для типичной задачи. При этом паттерн не готовое решение, а просто алгоритм действий, который должен привести к желаемому результату. Давайте рассмотрим один из наиболее часто используемых поведенческих паттернов — Шаблонный метод (Template method).
Как я уже писал ранее, существует три вида паттернов проектирования:
- Порождающиепаттерны позволяют возможность выполнять инициализацию объектов наиболее удобным и оптимальным способом.
- Структурныепаттерны описывают взаимоотношения между различными классами или объектами, позволяя им совместно реализовывать поставленную задачу.
- Поведенческиепаттерны позволяют грамотно организовать связь между сущностями для оптимизации и упрощения их взаимодействия.
Шаблонный метод (Template method) — это поведенческий паттерн, который позволяет определить каркас алгоритма, обособившись от конкретной реализации всех или некоторых шагов, позволяя делать это подклассам, при этом не нарушая структурной целостности алгоритма. То есть, Шаблонный метод задет основу алгоритма и позволяет наследникам вносить поправки, не меняя алгоритм в целом.
Проблема
Ваша команда разрабатывает приложение, работающее с геоданными в виде графа. Узлами графа являются городские локации: памятники, театры, рестораны, важные предприятия и прочее. Каждый узел имеет ссылки на другие, ближайшие к нему узлы. Каждому типу узлов соответствует свой класс, а каждый узел представлен отдельным объектом.
Экспорт геоузлов в XML.
Ваша задача — сделать экспорт этого графа в XML. Дело было бы плёвым, если бы вы могли редактировать классы узлов. Достаточно было бы добавить метод экспорта в каждый тип узла, а затем, перебирая узлы графа, вызывать этот метод для каждого узла. Благодаря полиморфизму, решение получилось бы изящным, так как вам не пришлось бы привязываться к конкретным классам узлов.
Но, к сожалению, классы узлов вам изменить не удалось. Системный архитектор сослался на то, что код классов узлов сейчас очень стабилен, и от него многое зависит, поэтому он не хочет рисковать и позволять кому-либо его трогать.
Код XML-экспорта придётся добавить во все классы узлов, а это слишком накладно.
К тому же он сомневался в том, что экспорт в XML вообще уместен в рамках этих классов. Их основная задача была связана с геоданными, а экспорт выглядит в рамках этих классов чужеродно.
Проблема
Представьте, что вы работаете над программой текстового редактора. Дело как раз подошло к разработке панели управления. Вы создали класс красивых и хотите использовать его для всех кнопок приложения, начиная от панели управления, заканчивая простыми кнопками в диалогах.
Все кнопки приложения унаследованы от одного класса.
Все эти кнопки, хоть и выглядят схоже, но делают разные вещи. Поэтому возникает вопрос: куда поместить код обработчиков кликов по этим кнопкам? Самым простым решением было бы создать подклассы для каждой кнопки и переопределить в них метод действия под разные задачи.
Множество подклассов кнопок.
Но скоро стало понятно, что такой подход никуда не годится. Во-первых, получается очень много подклассов. Во-вторых, код кнопок, относящийся к графическому интерфейсу, начинает зависеть от классов бизнес-логики, которая довольно часто меняется.
Несколько классов дублируют одну и ту же функциональность.
Решение
Паттерн Стратегия предлагает определить семейство схожих алгоритмов, которые часто изменяются или расширяются, и вынести их в собственные классы, называемые стратегиями.
Вместо того, чтобы изначальный класс сам выполнял тот или иной алгоритм, он будет играть роль контекста, ссылаясь на одну из стратегий и делегируя ей выполнение работы. Чтобы сменить алгоритм, вам будет достаточно подставить в контекст другой объект-стратегию.
Важно, чтобы все стратегии имели общий интерфейс. Используя этот интерфейс, контекст будет независимым от конкретных классов стратегий
С другой стороны, вы сможете изменять и добавлять новые виды алгоритмов, не трогая код контекста.
Стратегии построения пути.
В нашем примере каждый алгоритм поиска пути переедет в свой собственный класс. В этих классах будет определён лишь один метод, принимающий в параметрах координаты начала и конца пути, а возвращающий массив точек маршрута.
Хотя каждый класс будет прокладывать маршрут по-своему, для навигатора это не будет иметь никакого значения, так как его работа заключается только в отрисовке маршрута. Навигатору достаточно подать в стратегию данные о начале и конце маршрута, чтобы получить массив точек маршрута в оговорённом формате.
Проблема
На досуге вы решили написать небольшую игру, в которой игроки перемещаются по карте и стреляют друг в друга. Фишкой игры должна была стать реалистичная система частиц. Пули, снаряды, осколки от взрывов — всё это должно красиво летать и радовать взгляд.
Игра отлично работала на вашем мощном компьютере. Однако ваш друг сообщил, что игра начинает тормозить и вылетает через несколько минут после запуска. Покопавшись в логах, вы обнаружили, что игра вылетает из-за недостатка оперативной памяти. У вашего друга компьютер значительно менее «прокачанный», поэтому проблема у него и проявляется так быстро.
И действительно, каждая частица представлена собственным объектом, имеющим множество данных. В определённый момент, когда побоище на экране достигает кульминации, новые объекты частиц уже не вмещаются в оперативную память компьютера, и программа вылетает.
Посредник (Mediator)
Википедия гласит:
Пример из жизни: Общим примером будет, когда вы говорите с кем-то по мобильнику, то между вами и собеседником находится мобильный оператор. То есть сигнал передаётся через него, а не напрямую. В данном примере оператор — посредник.
Простыми словами: Шаблон посредник подразумевает добавление стороннего объекта (посредника) для управления взаимодействием между двумя объектами (коллегами). Шаблон помогает уменьшить связанность (coupling) классов, общающихся друг с другом, ведь теперь они не должны знать о реализациях своих собеседников.
Разберем пример в коде. Простейший пример: чат (посредник), в котором пользователи (коллеги) отправляют друг другу сообщения.
Изначально у нас есть посредник :
Затем у нас есть наши (коллеги):
Пример использования:
Примеры на Java и Python.
Псевдокод
В этом примере паттерн Команда служит для ведения истории выполненных операций, позволяя отменять их, если потребуется.
Пример реализации отмены в текстовом редакторе.
Команды, которые меняют состояние редактора (например, команда вставки текста из буфера обмена), сохраняют копию состояния редактора перед выполнением действия. Копии выполненных команд помещаются в историю команд, откуда они могут быть получены, если нужно будет сделать отмену операции.
Классы элементов интерфейса, истории команд и прочие не зависят от конкретных классов команд, так как работают с ними через общий интерфейс. Это позволяет добавлять в приложение новые команды, не изменяя существующий код.
Приспособленец (Flyweight)
Википедия гласит:
Пример из жизни: Вы когда-нибудь заказывали чай в уличном ларьке? Там зачастуют готовят не одну чашку, которую вы заказали, а гораздо большую емкость. Это делается для того, чтобы экономить ресурсы (газ/электричество). Газ/электричество в этом примере и являются приспособленцами, ресурсы которых делятся (sharing).
Простыми словами: Приспособленец используется для минимизации использования памяти или вычислительной стоимости путем разделения ресурсов с наибольшим количеством похожих объектов.
Перейдем к примерам в коде. Возьмем наш пример с чаем. Изначально у нас есть различные виды и :
Теперь у нас есть , который принимает заказы и выполняет их:
Пример использования:
Примеры на Java и Python.
Команда (Command)
Википедия гласит:
Пример из жизни: Типичный пример: вы заказываете еду в ресторане. Вы (т.е. ) просите официанта (например, ) принести еду (то есть ), а официант просто переправляет запрос шеф-повару (то есть ), который знает, что и как готовить. Другим примером может быть то, что вы () включаете () телевизор () с помощью пульта дистанционного управления ().
Простыми словами: Позволяет вам инкапсулировать действия в объекты. Основная идея, стоящая за шаблоном — это предоставление средств, для разделения клиента и получателя.
Вебинар Java, Containers and IntelliJ IDEA
17 декабря в 18:00, Онлайн, Беcплатно
tproger.ru
События и курсы на tproger.ru
Обратимся к коду. Изначально у нас есть получатель , в котором есть реализация каждого действия, которое может быть выполнено:
Затем у нас есть интерфейс , который каждая команда должна реализовывать, и затем у нас будет набор команд:
Затем у нас есть , с которым клиент будет взаимодействовать для обработки любых команд:
Наконец, мы можем увидеть, как использовать нашего клиента:
Шаблон команда может быть использован для реализации системы, основанной на транзакциях, где вы сохраняете историю команд, как только их выполняете. Если окончательная команда успешно выполнена, то все хорошо, иначе алгоритм просто перебирает историю и продолжает выполнять отмену для всех выполненных команд.
Примеры на Java и Python.
Проблема
Ви пишете програму для дата-майнінгу в офісних документах. Користувачі завантажуватимуть до неї документи різних форматів (PDF, DOC, CSV), а програма повинна видобути з них корисну інформацію.
У першій версії ви обмежилися обробкою тільки DOC файлів. У наступній версії додали підтримку CSV. А через місяць «прикрутили» роботу з PDF документами.
Класи дата-майнінгу містять багато дублювань.
В якийсь момент ви помітили, що код усіх трьох класів обробки документів хоч і відрізняється в частині роботи з файлами, але містить досить багато спільного в частині самого видобування даних. Було б добре позбутися від повторної реалізації алгоритму видобування даних у кожному з класів.
Плюсы
В сравнении с полностью самостоятельным проектированием, шаблоны обладают рядом преимуществ. Основная польза от использования шаблонов состоит в снижении сложности разработки за счёт готовых абстракций для решения целого класса проблем. Шаблон даёт решению своё имя, что облегчает коммуникацию между разработчиками, позволяя ссылаться на известные шаблоны. Таким образом, за счёт шаблонов производится унификация деталей решений: модулей, элементов проекта, — снижается количество ошибок. Применение шаблонов концептуально сродни использованию готовых библиотек кода. Правильно сформулированный шаблон проектирования позволяет, отыскав удачное решение, пользоваться им снова и снова. Набор шаблонов помогает разработчику выбрать возможный, наиболее подходящий вариант проектирования.
Концептуальный пример
Этот пример показывает структуру паттерна Шаблонный метод, а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
main.rb: Пример структуры паттерна
# Абстрактный Класс определяет шаблонный метод, содержащий скелет некоторого # алгоритма, состоящего из вызовов (обычно) абстрактных примитивных операций. # # Конкретные подклассы должны реализовать эти операции, но оставить сам # шаблонный метод без изменений. # # @abstract class AbstractClass # Шаблонный метод определяет скелет алгоритма. def template_method base_operation1 required_operations1 base_operation2 hook1 required_operations2 base_operation3 hook2 end # Эти операции уже имеют реализации. def base_operation1 puts 'AbstractClass says: I am doing the bulk of the work' end def base_operation2 puts 'AbstractClass says: But I let subclasses override some operations' end def base_operation3 puts 'AbstractClass says: But I am doing the bulk of the work anyway' end # А эти операции должны быть реализованы в подклассах. # # @abstract def required_operations1 raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" end # @abstract def required_operations2 raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" end # Это «хуки». Подклассы могут переопределять их, но это не обязательно, # поскольку у хуков уже есть стандартная (но пустая) реализация. Хуки # предоставляют дополнительные точки расширения в некоторых критических местах # алгоритма. def hook1; end def hook2; end end # Конкретные классы должны реализовать все абстрактные операции базового класса. # Они также могут переопределить некоторые операции с реализацией по умолчанию. class ConcreteClass1 < AbstractClass def required_operations1 puts 'ConcreteClass1 says: Implemented Operation1' end def required_operations2 puts 'ConcreteClass1 says: Implemented Operation2' end end # Обычно конкретные классы переопределяют только часть операций базового класса. class ConcreteClass2 < AbstractClass def required_operations1 puts 'ConcreteClass2 says: Implemented Operation1' end def required_operations2 puts 'ConcreteClass2 says: Implemented Operation2' end def hook1 puts 'ConcreteClass2 says: Overridden Hook1' end end # Клиентский код вызывает шаблонный метод для выполнения алгоритма. Клиентский # код не должен знать конкретный класс объекта, с которым работает, при условии, # что он работает с объектами через интерфейс их базового класса. # # @param abstract_class def client_code(abstract_class) # ... abstract_class.template_method # ... end puts 'Same client code can work with different subclasses:' client_code(ConcreteClass1.new) puts "\n" puts 'Same client code can work with different subclasses:' client_code(ConcreteClass2.new)
output.txt: Результат выполнения
Same client code can work with different subclasses: AbstractClass says: I am doing the bulk of the work ConcreteClass1 says: Implemented Operation1 AbstractClass says: But I let subclasses override some operations ConcreteClass1 says: Implemented Operation2 AbstractClass says: But I am doing the bulk of the work anyway Same client code can work with different subclasses: AbstractClass says: I am doing the bulk of the work ConcreteClass2 says: Implemented Operation1 AbstractClass says: But I let subclasses override some operations ConcreteClass2 says: Overridden Hook1 ConcreteClass2 says: Implemented Operation2 AbstractClass says: But I am doing the bulk of the work anyway