Базовые паттерны проектирования. Паттерны ооп в метафорах

Перед началом статьи, я хочу предложить доклад своего товарища — Дениса Порпленко о паттернах проектирования в программировании:

Я хочу напомнить о том, что паттерны проектирования возникли как решение часто возникающих однотипных задач, собственно, в проектировании. Но основное свое развитие получили в программной разработке. И дальше буду продолжать повествование в контексте разработки программного обеспечения.

Тематика паттернов очень широка, паттерны проектирования можно разделить на такие типы, как шаблоны для программирования, шаблоны архитектуры системы и шаблоны хранения данных. Это самые популярные паттерны, есть и другие. Ниже я их все перечислю.

Многие компании используют в своей практике паттерны. Применение паттернов имеет свои плюсы и минусы.
Польза от применения шаблонов проектирования :
— основное преимущество использования шаблонов перед свободным проектированием состоит в том, что паттерн дает название проблеме и определяет способы решения многих проблем за счет готового набора абстракций
— облегчает коммуникацию между разработчиками системы
— использование шаблонов проектирования аналогично использованию готовых библиотек кода
правильное использование шаблонов помогает разработчикам определить нужный вектор развития и уйти от многих проблем, которые могут возникнуть в процессе разработки.

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

Основные шаблоны программирования

Фундаментальные

Шаблон делегирования (Delegation pattern) — Объект внешне выражает некоторое поведение, но в реальности передаёт ответственность за выполнение этого поведения связанному объекту.
Шаблон функционального дизайна (Functional design) — Гарантирует, что каждый модуль компьютерной программы имеет только одну обязанность и исполняет её с минимумом побочных эффектов на другие части программы.
Неизменяемый интерфейс (Immutable interface) — Создание неизменяемого объекта.
Интерфейс (Interface) — Общий метод для структурирования компьютерных программ для того, чтобы их было проще понять.
Интерфейс-маркер (Marker interface) — В качестве атрибута (как пометки объектной сущности) применяется наличие или отсутствие реализации интерфейса-маркера. В современных языках программирования вместо этого могут применяться атрибуты или аннотации.
Контейнер свойств (Property container) — Позволяет добавлять дополнительные свойства для класса в контейнер (внутри класса), вместо расширения класса новыми свойствами.
Событийный шаблон (Event channel) — Расширяет шаблон Publish/Subscribe, создавая централизованный канал для событий. Использует объект-представитель для подписки и объект-представитель для публикации события в канале. Представитель существует отдельно от реального издателя или подписчика. Подписчик может получать опубликованные события от более чем одного объекта, даже если он зарегистрирован только на одном канале.

Порождающие шаблоны

Порождающие шаблоны (Creational) — шаблоны проектирования, которые абстрагируют процесс инстанцирования. Они позволяют сделать систему независимой от способа создания, композиции и представления объектов. Шаблон, порождающий классы, использует наследование, чтобы изменять инстанцируемый класс, а шаблон, порождающий объекты, делегирует инстанцирование другому объекту.
Абстрактная фабрика (Abstract factory) — Класс, который представляет собой интерфейс для создания компонентов системы.
Строитель (Builder) — Класс, который представляет собой интерфейс для создания сложного объекта.
Фабричный метод (Factory method) — Определяет интерфейс для создания объекта, но оставляет подклассам решение о том, какой класс инстанцировать.
Отложенная инициализация (Lazy initialization) — Объект, инициализируемый во время первого обращения к нему.
Пул одиночек (Multiton) — Гарантирует, что класс имеет поименованные экземпляры объекта и обеспечивает глобальную точку доступа к ним.
Объектный пул (Object pool) — Класс, который представляет собой интерфейс для работы с набором инициализированных и готовых к использованию объектов.
Прототип (Prototype) — Определяет интерфейс создания объекта через клонирование другого объекта вместо создания через конструктор.
Получение ресурса есть инициализация (Resource acquisition is initialization (RAII)) — Получение некоторого ресурса совмещается с инициализацией, а освобождение - с уничтожением объекта.
Одиночка (Singleton) — Класс, который может иметь только один экземпляр.

Структурные шаблоны

Структурные шаблоны (Structural) определяют различные сложные структуры, которые изменяют интерфейс уже существующих объектов или его реализацию, позволяя облегчить разработку и оптимизировать программу.
Адаптер (Adapter / Wrapper) — Объект, обеспечивающий взаимодействие двух других объектов, один из которых использует, а другой предоставляет несовместимый с первым интерфейс.
Мост (Bridge) — Структура, позволяющая изменять интерфейс обращения и интерфейс реализации класса независимо.
Компоновщик (Composite) — Объект, который объединяет в себе объекты, подобные ему самому.
Декоратор или Обёртка (Decorator) или (Wrapper) — Класс, расширяющий функциональность другого класса без использования наследования.
Фасад (Facade) — Объект, который абстрагирует работу с несколькими классами, объединяя их в единое целое.
Единая точка входа (Front controller) — Обеспечивает унифицированный интерфейс для интерфейсов в подсистеме. Front Controller определяет высокоуровневый интерфейс, упрощающий использование подсистемы.
Приспособленец (Flyweight) — Это объект, представляющий себя как уникальный экземпляр в разных местах программы, но по факту не являющийся таковым.
Заместитель (Proxy) — Объект, который является посредником между двумя другими объектами, и который реализует/ограничивает доступ к объекту, к которому обращаются через него.

Поведенческие шаблоны

Поведенческие шаблоны (Behavioral) определяют взаимодействие между объектами, увеличивая таким образом его гибкость.
Цепочка обязанностей (Chain of responsibility) — Предназначен для организации в системе уровней ответственности.
Команда (Command) — Представляет действие. Объект команды заключает в себе само действие и его параметры.
Интерпретатор (Interpreter) — Решает часто встречающуюся, но подверженную изменениям, задачу.
Итератор (Iterator) — Представляет собой объект, позволяющий получить последовательный доступ к элементам объекта-агрегата без использования описаний каждого из объектов, входящих в состав агрегации.
Посредник (Mediator) — Обеспечивает взаимодействие множества объектов, формируя при этом слабую связанность и избавляя объекты от необходимости явно ссылаться друг на друга.
Хранитель (Memento) — Позволяет не нарушая инкапсуляцию зафиксировать и сохранить внутренние состояния объекта так, чтобы позднее восстановить его в этих состояниях.
Нулевой объект (Null object) — Предотвращает нулевые указатели, предоставляя объект «по умолчанию».
Наблюдатель (Observer) — Определяет зависимость типа «один ко многим» между объектами таким образом, что при изменении состояния одного объекта все зависящие от него оповещаются об этом событии.
Слуга (Servant) — Используется для обеспечения общей функциональности группе классов.
Спецификация (Specification) — Служит для связывания бизнес-логики.
Состояние (State) — Используется в тех случаях, когда во время выполнения программы объект должен менять своё поведение в зависимости от своего состояния.
Стратегия (Strategy) — Предназначен для определения семейства алгоритмов, инкапсуляции каждого из них и обеспечения их взаимозаменяемости.
Шаблонный метод (Template method) — Определяет основу алгоритма и позволяет наследникам переопределять некоторые шаги алгоритма, не изменяя его структуру в целом.
Посетитель (Visitor) — Описывает операцию, которая выполняется над объектами других классов. При изменении класса Visitor нет необходимости изменять обслуживаемые классы.
Простая политика — я знаю, что такой паттерн есть, но что он означает, пока не нашел. Если будет инфа — скиньте в комментариях.
Слушатель (Event listener) — аналогично
Одноразовый посетитель (Single-serving visitor) — Оптимизирует реализацию шаблона посетитель, который инициализируется, единожды используется, и затем удаляется.
Иерархический посетитель (Hierarchical visitor) — Предоставляет способ обхода всех вершин иерархической структуры данных (например, древовидной).

Шаблоны параллельного программирования

Используются для более эффективного написания многопоточных программ, и предоставляет готовые решения проблем синхронизации.
Активный объект (Active Object) — Служит для отделения потока выполнения метода от потока, в котором он был вызван. Использует шаблоны асинхронный вызов методов и планировщик.
Уклонитель (Balking) — Служит для выполнения действия над объектом только тогда, когда тот находится в корректном состоянии.
Привязка свойств (Binding properties) — Комбинирует несколько наблюдателей для обеспечения синхронизации свойств в различных объектах
Обмен сообщениями (Messaging design pattern (MDP)) — Позволяет компонентам и приложениям обмениваться информацией (сообщениями).
Блокировка с двойной проверкой (Double-checked locking) — Предназначен для уменьшения накладных расходов, связанных с получением блокировки.
Ассинхронные события (Event-based asynchronous) — Адресные проблемы с Асинхронным паттерном, которые возникают в программах с несколькими потоками.
Охраняемая приостановка (Guarded suspension) — Используется для блокировки выполнения действия над объектом только тогда, когда тот находится в корректном состоянии.
Полусинхронизация (Half-Sync/Half-Async) — пока нет данных про этот паттерн.
Лидеры (Leaders/followers) — пока нет данных про этот паттерн.
Замок (Lock) — Один поток блокирует ресурс для предотвращения доступа или изменения его другими потоками.
Монитор (Monitor object) — Объект, предназначенный для безопасного использования более чем одним потоком.
Реактор (Reactor) — Предназначен для синхронной передачи запросов сервису от одного или нескольких источников.
Блокировка чтение-запись (Read write lock) — Позволяет нескольким потокам одновременно считывать информацию из общего хранилища, но позволяя только одному потоку в текущий момент времени её изменять.
Планировщик (Scheduler) — Обеспечивает механизм реализации политики планирования, но при этом не зависящих ни от одной конкретной политики.
Пул потоков (Thread pool) — Предоставляет пул потоков для обработки заданий, представленных обычно в виде очереди.
Спецпотоковое хранилище (Thread-specific storage) — Служит для предоставления различных глобальных переменных для разных потоков.
Однопоточное выполнение (Single thread execution) — Препятствует конкурентному вызову метода, тем самым запрещая параллельное выполнение этого метода.
Кооперативный паттерн (Cooperative pattern) — Обеспечивает механизм безопасной остановки потоков исполнения, используя общий флаг для сигнализирования прекращения работы потоков.

Шаблоны архитектуры системы

Model-View-Controller (MVC) — Модель-представление-контроллер.
Model-View-Presenter
Model-View-View Model
Presentation-Abstraction-Control
Naked objects
Hierarchical Model–View–Controller

Enterprise шаблоны

Active Record - способ доступа к данным реляционных баз данных в объектно-ориентированном программировании.
Business Delegate
Composite Entity/Составная Сущность
Composite View
DAO (Data Access Object) Объект Доступа к Данным
Dispatcher View
Front Controller
Intercepting Filter
Registry
Service Activator
Service Locator/Локатор Службы
Service to Worker
Session Facade/Фасад Сессии
Transfer Object Assembler
Transfer Object/Объект Перемещения
Value List Handler/Обработчик Списка Значений
View Helper
Unit of Work

Другие типы шаблонов

Также на сегодняшний день существует ряд других шаблонов.
Хранилище (Repository)
Carrier Rider Mapper описывают предоставление доступа к хранимой информации.
Аналитические шаблоны описывают основной подход для составления требований для программного обеспечения (requirement analysis) до начала самого процесса программной разработки
Коммуникационные шаблоны описывают процесс общения между отдельными участниками/сотрудниками организации
Организационные шаблоны описывают организационную иерархию предприятия/фирмы
Антипаттерны (Anti-Design-Patterns) описывают, как не следует поступать при разработке программ, показывая характерные ошибки в дизайне и в реализации

При реализации проектов по разработке программных систем и моделированию бизнес-процессов встречаются ситуации, когда решение проблем в различных проектах имеют сходные структурные черты. Попытки выявить похожие схемы или структуры в рамках объектно-ориентированного анализа и проектирования привели к появлению понятия паттерна, которое из абстрактной категории превратилось в непременный атрибут современных CASE-средств

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

  • Архитектурные паттерны
  • Паттерны проектирования
  • Паттерны анализа
  • Паттерны тестирования
  • Паттерны реализации

Архитектурные паттерны(Architectural patterns) - множество предварительно определенных подсистем со спецификацией их ответственности, правил и базовых принципов установления отношений между ними.

Архитектурные паттерны предназначены для спецификации фундаментальных схем структуризации программных систем. Наиболее известными паттернами этой категории являются паттерны GRASP ( General Responsibility Assignment Software Pattern ). Эти паттерны относятся к уровню системы и подсистем, но не к уровню классов. Как правило, формулируются в обобщенной форме, используют обычную терминологию и не зависят от области приложения. Паттерны этой категории систематизировал и описал К. Ларман.

Паттерны проектирования (Design patterns) - специальные схемы для уточнения структуры подсистем или компонентов программной системы и отношений между ними.

Паттерны проектирования описывают общую структуру взаимодействия элементов программной системы, которые реализуют исходную проблему проектирования в конкретном контексте. Наиболее известными паттернами этой категории являются паттерны GoF ( Gang of Four), названные в честь Э. Гаммы, Р. Хелма, Р. Джонсона и Дж. Влиссидеса, которые систематизировали их и представили общее описание. Паттерны GoF включают в себя 23 паттерна. Эти паттерны не зависят от языка реализации, но их реализация зависит от области приложения.

Паттерны анализа (Analysis patterns) - специальные схемы для представления общей организации процесса моделирования.

Паттерны анализа относятся к одной или нескольким предметным областям и описываются в терминах предметной области . Наиболее известными паттернами этой группы являются паттерны бизнес-моделирования ARIS ( Architecture of Integrated Information Systems), которые характеризуют абстрактный уровень представления бизнес-процессов. В дальнейшем паттерны анализа конкретизируются в типовых моделях с целью выполнения аналитических оценок или имитационного моделирования бизнес-процессов.

Паттерны тестирования (Test patterns) - специальные схемы для представления общей организации процесса тестирования программных систем., паттерны устойчивости кода, паттерны разработки графического интерфейса пользователя и др. Паттерны этой категории описаны в работах М. Гранда, К. Бека, Дж. Тидвелла и др. Некоторые из них реализованы в популярных интегрированных средах программирования в форме шаблонов создаваемых проектов. В этом случае выбор шаблона программного приложения позволяет получить некоторую заготовку программного кода.

В паттерне "Модель - представление - контроллер " модель представляет данные приложения и связанную с ними бизнес-логику. Модель может быть представлена одним объектом или сложным графом связанных объектов. В приложении для плат­формы Java ЕЕ данные инкапсулируются в объектах предметной области, часто раз­вертываемых в EJB-модуле. Данные передаются в БД и из нее в объектах передачи данных (ОТО), и к ним обращаются с помощью объектов доступа к данным (ОАО).

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

Язык Jаvа обеспечивает готовую для использования реализацию паттерна "Наблю­датель" . Разработчики легко могут реализовать этот паттерн с помощью интерфей­са Observer и расширения класса Observable .

Первое, что нам необходимо сделать - это создать класс, расширяющий класс Observable. В следующем примере когда новостное агентство оповещает несколько типов подпис­чиков в момент публикации нового материала. Подписчик может добавить соб­ственное поведение после получения обновления.

Язык Jаvа с самого начала поддерживал потоки, которые вы легко можете исполь­зовать для выполнения асинхронного кода :

public class AsyncRunnable implements Runnable { public void run() { System.out.println("Running!"); } }

public class AsyncRunnable implements Runnable {

public void run () {

System . out . println ("Running!" ) ;

Для выполнения класса Runnable инициализируйте его в потоке и вызовите метод run , обратившись к методу start() только что созданного потока.

Опубликовано в | Метки , | |

Паттерн программирования "Асинхронность " - особый, хорошо интегрирован­ный случай множественных потоков. Вследствие самой сущности потоков много­ поточные модели нуждаются в системах уведомления и зависят от шаблонного кода для запуска потоков.

Асинхронные обращения используются даже в одногопоточных средах, таких как NodeJS. Почти все пользовательские интерфейсы поддерживают асинхронное выполнение для удержания UI в активном, реагирующем на действия пользовате­ля состоянии.

Тем не менее асинхронное программирование может быть полезным и в других местах, помимо пользовательских интерфейсов, обычно на серверной стороне. Hи J2SE , ни J2ЕЕ не предоставляли встроенной "легкой" реализации для асинхрон­ного программирования.

Опубликовано в | Метки , | |

Паттерн "Декоратор" Java динамически добавляет объекту поведение во время выпол­нения или тогда, когда невозможно или нецелесообразно создавать производные классы (возможно, потому, что при этом создаются множественные подклассы).

Показывает, как добавить поведение к объекту пиццы во вре­мя выполнения на основе сделанного посетителем выбора.

Функциональность интерфейса программирования приложений (API ) может быть расширена и усовершенствована посредством оборачивания в декоратор. Подобным образом часто декорируются потоки данных. java.iо.BufferedInputStream - хороший пример декоратора, оборачивающего низкоуровневое API и добавля­ющего функциональность буферу потока ввода.

Материал статьи для уровня Beginners. Здесь не будет Moose, только чистый Perl. Предполагается, что какое-то ООП в Perl уже знакомо

Паттерны это стандартные приемы, решающие небольшую конкретную задачу. Это не инструкция, как писать код, а схема или принцип организации кода, модулей и т. п. Уверена, что если вы их не знаете на уровне диаграмм UML, то встречали в коде. Этот небольшой обзор познакомит с самыми простыми, полезными и часто используемыми паттернами.

Singleton (Одиночка)

Порождающий паттерн. Используется в случае, когда в системе должен быть только один экземпляр какого-то класса. Например, подключение к базе, распарсенный файл конфигурации и т. д. Но при этом вы не хотите таскать с собой какие-то глобальные переменные. Невероятно удобен для отложенных инициализаций тех же конфигов.

Реализация

Пусть у нас будет какой-то абстрактный класс с именем MyClass .

Package MyClass; use strict; our $singleton = undef; sub new { my $class = shift; return $singleton if defined $singleton; my $self = {}; $singleton = bless($self, $class); $singleton->init(); return $singleton; } # other methods sub init { #... } 1;

$singleton->init(); - вот тут, к примеру, проводится какая-то инициализация (либо она может быть отложена до вызова конкретных функций).

Пример использования

use MyClass; use strict; sub f { print MyClass->new()->{name}, "\n"; } sub f2 { print MyClass->new()->{name}, "\n"; } my $obj = MyClass->new(); $obj->{name} = "Bob"; # это не ООП! f(); f2(); $obj->{name} = "Mike"; # и это тоже f(); f2();

На выходе

Bob Bob Mike Mike

В результате вызова функций f() и f2() мы получим один и тот же созданный объект, ссылка на который хранится у нас в $MyClass::singleton , с ней можно работать напрямую, но это моветон и делать так не надо (за исключением ситуаций, когда требуется высокая производительность, а использование аксессоров создаёт ощутимые накладные расходы).

Таким образом, можно в любом месте кода создавать объект через конструктор и не волноваться, что он каждый раз будет создаваться заново.

На CPAN, кстати, есть Class::Singleton , MooseX::Singleton , Apache::Singleton и еще куча других.

Abstract Factory (Абстрактная фабрика)

Порождающий паттерн. Берет на себя ответственность за создание объекта нужного класса. Мы просто обращаемся к ее конструктору, а какой нам вернуть объект, фабрика решает сама. Создаваемые объекты, конечно, должны быть из одного семейства и иметь идентичный интерфейс. То есть, они должны быть взаимозаменяемыми.

В качестве примеров использования: в номере 21 в статье паттерн использован для создания объекта-логгера в зависимости от способа вывода: либо stderr, либо file. В более бизнесовом мире встречаются разные способы доставки (там все одинаковое, но разные формочки, разные коэффициенты какие-нибудь), разные форматы прайсов от поставщиков (у кого-то Excel, у кого-то XML), разные способы отправки уведомлений (e-mail, SMS).

У меня будет пример очень абстрактный, но очень понятный. Допустим, у нас есть ферма с животными. Нам, с точки зрения логики, все равно, какое животное будет создано, мы только задаем в параметрах, сколько у него ног. (В реальности значение количества ног мы получаем из внешнего конфига, а не задаем в коде).

Пример использования

use AnimalFactory; my $animal_one = AnimalFactory->new(legs => 2); print ref $animal_one, "\n"; my $animal_two = AnimalFactory->new(legs => 4); print ref $animal_two, "\n"; $animal_one->walk(); $animal_two->walk();

На выходе

Chicken Cow

Реализация

package AnimalFactory; use Chicken; use Cow; sub new { my $class = shift; my $opt = {@_}; return Cow->new() if $opt->{legs} == 4; return Chicken->new() if $opt->{legs} == 2; } 1;

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

Если нам понадобится класс Snake , то мы просто добавим логику его создания в AnimalFactory , как-нибудь так:

Return Snake->new() if $opt->{legs} == 0;

Если вдруг Cow нужно будет заменить на Horse , это нужно будет сделать только в одном месте - в AnimalFactory , не затрагивая других участков кода.

Абстрактную фабрику стоит использовать там, где класс объекта зависит от каких-нибудь внешних факторов: пользовательских настроек, версии браузера, ОС и т. п.

(В некоторых случаях не очень хорошо, что мы подгружаем все возможные классы сразу через use , это можно изменить: внести внутрь конструктора и подключать классы через require уже после анализа параметров и до создания конкретного объекта.)

Template Method (Шаблонный метод)

Паттерн поведения. Паттерн используется для определения основного алгоритма для всех подклассов. Берем алгоритм, делим его на много мелких этапов, пишем в базовом классе, а все подклассы реализуют различающиеся части.

Самый простой пример: импорт товаров от поставщика. Нужно распарсить файл, пройти по всем товарам от поставщика, если товар найден - обновить его, если не найден - создать, подсчитать конечную стоимость, записать операцию с товаром в журнал, проделать что-нибудь еще с чем-нибудь.

Использование

my $import = ImportFactory->new(type => "Bekka"); $import->do;

(Здесь я использую фабрику для создания нужного мне объекта по имени поставщика, от которого загружается файл.)

Но можно обойтись и без фабрики, а сделать вот так (хотя гибкость это явно снижает, но она и не всегда такая нужна):

My $type = "Bekka"; my $import = $type->new(); $import->do;

Реализация

Допустим, у меня тут два поставщика: Bekka

Package Bekka; use base "Import"; sub parse { # parse Excel } sub count_price { # price * 2 } 1;

который присылает файлы в Excel, и у которого цену из файла нужно увеличивать в два раза.

И Pukka , у которого файлы в XML, а цену нужно делить пополам:

Package Pukka; use base "Import"; sub parse { # parse XML } sub count_price { # price / 2 } 1;

Оба эти класса имеют родителя Import , который и описывает основной алгоритм загрузки файла (sub do). В нем определяются все используемые методы, но работающие по какому-то умолчанию. (У методов, конечно, еще есть какой-нибудь код, но здесь он не нужен, поэтому его не привожу.)

Package Import; ... sub do { my $self = shift; $self->parse(); while ($self->next) { if ($self->find) { $self->update; } else { $self->insert; } $self->count_price; $self->log; } $self->finish; } sub next; sub find; sub update; sub insert; sub count_price { my $self = shift; # use original price } 1;

Получается: фабрика создает нам объект нужного класса, основываясь на имени поставщика. Базовый объект для него описывает весь процесс импорта товара от любого поставщика. Объект конкретного класса переопределяет те методы, которые ему не подходят, на свою реализацию - в нашем случае методы count_price и parse .

Метод do из класса Import и есть наш шаблонный метод - он описывает шаблон поведения. И вовсе необязательно, что он должен его реализовывать. В реальности сложно найти задачи такого плана, которые могут быть удовлетворены поведением по умолчанию.

Удобно использовать констукцию can для методов, которые не обязательно должны быть в базовом классе, но могут быть в подклассах: $self->do_smth if $self->can("do_smth") , тогда метод будет вызваться только в том случае, если он реально определен. Это избавит от кучи пустого кода, а также позволяет писать довольно удобно хуки, типа:

$self->before_update() if $self->can("before_update"); $self->update(); $self->after_update() if $self->can("after_update");

Strategy (Стратегия)

Паттерн поведения. Другое название - Политика. Используется для взаимозаменяемости алгоритмов или их фрагментов. Например, когда у нас есть разные способы расчета скидки на заказ. (Пример высосан из пальца, и для таких случаев делать подобные схемы - роскошь. Но он прост и понятен.)

Использование

use DiscountFactory; use Order; my $order = Order->new(); $order->{summa} = 200; # так делать - не ООП! Это только для примера my $discounter = DiscountFactory->new(type => "Visa"); print $order->get_summa(discounter => $discounter), "\n"; $discounter = DiscountFactory->new(type => "yandex"); print $order->get_summa(discounter => $discounter), "\n";

На выходе

Реализация

Класс Заказ

Package Order; sub new { return bless {}, shift } sub get_summa { my $self = shift; my $opt = {@_}; my $summa = $opt->{discounter}->do(summa => $self->{ summa }); return $summa; } 1;

Фабрика DiscountFactory (ее кода здесь нет, там все как и в обычной фабрике) возвращает объекты класса либо DiscountVisa , либо DiscountYM:

Package DiscountVisa; sub new { return bless {}, shift } sub do { my $self = shift; my $opt = {@_}; # Здесь я позволила себе использовать # «магическое число» --- это только для наглядности # примера. Так делать плохо. return $opt->{summa} * (1 - 0.02); } package DiscountYM; sub new { return bless {}, shift } sub do { my $self = shift; my $opt = {@_}; return $opt->{summa} * (1 + 0.05); } 1;

В классе Order у нас есть метод get_summa , который возвращает конечную стоимость заказа, но он должен учитывать и скидку на заказ. А скидка на заказ определяется способом оплаты заказа.

my $discounter = DiscountFactory->new(type => "Visa") - создали наш объект-дискаунтер, который знает, как считать скидку при оплате картой Visa.

$order->get_summa(discounter => $discounter) - вызываем метод для получения итоговой стоимости заказа, передавая туда нашу «стратегию» расчета скидки.

my $summa = $opt->{discounter}->do(summa => $self->{ summa }); - в методе get_summa мы вызываем операцию применения скидки к нашей базовой стоимости заказа.

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

На деле все очень просто, в следующей статье обязательно расскажу про другие очень используемые паттерны с чуть более сложной реализацией.

Приложения Laravel, нам неплохо было бы понять архитектуру каркаса приложения . Некоторые могут спросить: Зачем это нужно? Смысл забивать себе голову разной теоретической чепухой?
Постараюсь ответить на этот вопрос примером из жизни:

Представьте себе, что вы хотите иметь эксклюзивный автомобиль. Естественно он будет строиться на базе некоей серийной модели, но для того, чтобы его построить вам потребуется помощь людей, которые умеют это делать. Вы решаете обратиться к мастерам или даже к фирмам, у которых есть все необходимое: знания, опыт, оборудование и инструмент, но их услуги не дешевы, Ваш бюджет просто не выдержит таких расходов. Тогда вы решаете, что будете строить автомобиль самостоятельно. Отличное решение - правда, вы берете в руки инструмент, плюете на изучение теории, внедряете свои задумки и в итоге…

Я думаю, не стоит описывать, что у вас получится в итоге, ясно только одно - ничего хорошего.

Ну и более приближенный к теме Laravel 4 ответ: Понимание архитектуры приложения , знание основ паттернов (шаблонов) проектирования , помогут вам понять, почему следует использовать именно этот каркас web-приложения, оценить его слабые и сильные стороны. Так же вы получите ответ, почему так сильно изменился Laravel 4 по сравнению с Laravel 3.

Для тех, кто все же решил изучить теорию, прежде чем приступить к практике, советую: запаситесь терпением.

После прочтения статьи не останавливайтесь на достигнутом, в сети много ресурсов, где можно почерпнуть нужную информацию по паттернам. Поверьте, зная, что такое паттерны, их конкретные реализации и как их использовать, можно добиться намного большего, чем оставив эту тему за пределами багажа своих знаний…

Определения

  • Архитектура приложения - это логическая структура, описывающая отдельные компоненты, их свойства и связи в виде единой системы.
  • Паттерны - это описания схем детализации отдельных подсистем приложения и взаимосвязей между ними. При этом паттерны не являются частью программы, не влияют прямо на ее структуру и сохраняют полную независимость от языка программирования конкретной системы.
  • MVC - программная парадигма архитектурных паттернов : модель - представление - контроллер.

Паттерны это не так сложно как кажется

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

На самом деле паттерны не так сложны для понимания, как кажется на первый взгляд. Давайте проиллюстрируем их примерами, все сразу станет намного понятнее.

Итак, представьте, что вам необходимо разработать сложное приложение представлявшее каркас диалога и передачи данных между людьми, абстрактными Васей и Петей. Вначале мы рассмотрим простую программу того как Вася и Петя будут общаться между собой и производить обмен информацией:

  1. Вася и Петя встретились.
  2. Вася протянул Пете руку.
  3. Петя сказал «Здравствуй Вася» .
  4. Вася ответил «Здравствуй Петя» .
  5. Вася спросил «Ты принес мне диск с курсовой?» .
  6. Петя ответил «Да, принес» .
  7. Вася полез в карман и достал деньги.
  8. Вася отсчитал 100$.
  9. Вася передал деньги Пете.
  10. Петя принял деньги от Васи.
  11. Петя пересчитал деньги.
  12. Петя кивнул головой - подтверждая, что сумма соответствует цене за диск.
  13. Петя положил деньги в карман.
  14. Петя достал диск.
  15. Петя передал диск Васе.
  16. Вася принял диск от Пети.
  17. Вася положил диск в карман.

Теперь разобьём эту программу на отдельные части:

  1. Начало программы.
  2. Встреча.
  3. Обмен приветствиями.
  4. Проверка наличия «данных» .
  5. Передача данных «деньги» .
  6. Списание данных «деньги» .
  7. Проверка данных «деньги» .
  8. Запись данных «деньги» .
  9. Передача данных «диск» .
  10. Списание данных «диск» .
  11. Запись данных «диск» .
  12. Конец программы.

У нас получился алгоритм действий для встречи и обмена диска на деньги между двумя индивидуумами.

Теперь разбиваем алгоритм на составляющие так:

  1. Общение.
  2. Обмен данными.
  3. Проверка условий.
  4. Действия.

У нас как раз и получился набор паттернов (правда очень абстрактный). Зато этот архитектурный паттерн или программная парадигма описывает обмен данными практически любого вида и любой сложности. По сути это аналог всем известного MVC .

Если же мы решим разработать универсальную программу для взаимодействия двух человек, то нам будет недостаточно такого куцего описания. Точнее нам к архитектурному паттерну понадобиться добавить паттерны, которые расширят описание реализации взаимодействия всех частей архитектурного паттерна.

Давайте попробуем составить схему таких паттернов самостоятельно:

  1. Общение
    1. Установление контакта
    2. Передача запросов между пользователем и системой
  2. Обмен данными
    1. Передача данных запроса в направлении в систему
    2. Передача данных запроса в направлении из системы
    3. Передача команд на проверку условий
    4. Передача команд на передачу данных
  3. Проверка условий
    1. Проверка условий передачи данных
    2. Подтверждение проверки условий
  4. Действия
    1. Извлечение данных из хранилища «А»
    2. Передача данных в хранилище «А»

В итоге у нас получилась своя схема паттернов.

Теперь, если мы опишем, как конкретно взаимодействуют наши паттерны между собой - то получим рабочие паттерны. Причем паттерны будут достаточно универсальными, чтобы на их основе писать приложения не только для общения людей, но и для общения инопланетян.

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

Паттерны бывают разными

Давайте теперь немного углубим наши знания. Дело в том, что паттерны бывают не только архитектурными, на самом деле у паттернов есть несколько классификаций .

  • Классификация по масштабу
  • Классификация по стилю
  • Классификация по применению

Классификация по масштабу

Самая часто используемая классификация - это классификация по масштабу. Чаще всего она применяется для паттернов проектирования и делится на три слоя по детализации:

  • Архитектурные паттерны - наивысший слой детализации, используются для описания структуры программы в целом.
  • Паттерны проектирования - средний слой детализации, описывают компоненты отдельных архитектурных паттернов и реализацию их взаимодействия.
  • Идиомы - низший слой детализации, описывают реализацию отдельных решений проблем применительно к конкретному языку программирования. Следует учитывать, что зачастую идиомы для различных языков программирования имеют различную реализацию, или не имеют таковой вовсе. Примером могут служить указатели в памяти для реализации в Assembler , они имеют абсолютно другую реализацию в Си и не имеют реализации в С# , так как там проблема с утечкой памяти не существует ибо есть мусора ...

Классификация по стилю

  • Порождающие паттерны - предназначены для решения проблем создания новых объектов и связей.
  • Структурные паттерны - предназначены для компоновки системы, при этом могут использовать различные механизмы, такие как наследование , полиморфизм , композиция . Кстати, принципы классов построены как раз на структурных паттернах.
  • Паттерны поведения - предназначены для решения задач связи объектов и распределения задач между ними.

Классификация по применению

Программистам редко приходится сталкиваться с данным классом паттернов, но все же стоит о нем упомянуть, чтобы иметь хотя бы общее представление. Это самый высокоуровневый класс паттернов. В него входят целые классы паттернов. Например:

  • Паттерны тестирования
  • Паттерны документирования
  • Паттерны организации производственных процессов
  • Паттерны организации рабочих мест

И многие другие. Но я надеюсь их уже не нужно расписывать, вы вполне теперь способны продолжить этот список и без моих подсказок.

Заключение

Какие преимущества дают нам паттерны? Отвечу на этот вопрос, цитируя John Vlissides ( Влиссидес):

Паттерны суммируют опыт множества разработчиков и экспертов, делая его доступным рядовым разработчикам.
Именование паттернов позволяют создать своего рода словарь, с помощью которого разработчики могут понять друг друга намного лучше.
Если в документации к системе указано, на основе каких паттернов она построена, это позволяет быстрее понять структуру системы.

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

Для тех, кого заинтересовали паттерны, советую найти и почитать книги:

  • Э. Гамма, Р. Хелм, Р. Джонсон, Д. Влиссидес: Приемы ООП. проектирования .
  • Джон Влиссидес: шаблонов проектирования . Дополнительные штрихи.