Паттерны проектирования. Введение в паттерны проектирования

14.07.2019

Шаблон проектирования или паттерн (англ. design pattern ) в разработке программного обеспечения - повторимая архитектурная конструкция, представляющая собой решение проблемы проектирования в рамках некоторого часто возникающего контекста.

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

Классификация паттернов

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

    Архитектурные паттерны

    Паттерны проектирования

Архитектурные паттерны , являясь наиболее высокоуровневыми паттернами, описывают структурную схему программной системы в целом. В данной схеме указываются отдельные функциональные составляющие системы, называемые подсистемами, а также взаимоотношения между ними. Примером архитектурного паттерна является хорошо известная программная парадигма "модель-представление-контроллер" (model-view-controller - MVC).

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

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

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

Описание паттернов

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

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

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

    Решение. Здесь указывается, как именно данное решение связано с проблемой, приводится пути ее решения.

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

Результаты применения паттернов

    Они (паттерны) позволяют суммировать опыт экспертов и сделать его доступным рядовым разработчикам.

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

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

    Паттерны упрощают реструктуризацию системы независимо от того, использовались ли паттерны при ее проектировании.

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

http://citforum.ru/SE/project/pattern/#3

Типы шаблонов проектирования

Основные

Шаблон делегирования

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

Абстрактная фабрика, Прототип, Строитель

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

Адаптер, Мост, Приспособленец, Заместитель

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

Стратегия, Состояние

Concurrency - Параллелизм

Частные

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

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

Материал статьи для уровня 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 мы вызываем операцию применения скидки к нашей базовой стоимости заказа.

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

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

Последнее обновление: 31.10.2015

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

Хотя идея паттернов как способ описания решения распространенных проблем в области проектирования появилась довольно давно, но их популярность стала расти во многом благодаря известной работе четырех авторов Эриха Гаммы, Ричарда Хелма, Ральфа Джонсона, Джона Влиссидеса, которая называлась "Design Patterns: Elements of Reusable Object-Oriented Software" (на русском языке известна как "Приемы объектно-ориентированного проектирования. Паттерны проектирования") и которая вышла в свет в 1994 году. А сам коллектив авторов нередко называют "Банда четырёх" или Gang of Four или сокращенно GoF. Данная книга по сути являлась первой масштабной попыткой описать распространенные способы проектирования программ. И со временем применение паттернов стало считаться хорошей практикой программирования.

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

Причем паттерны, как правило, не зависят от языка программирования. Их принципы применения будут аналогичны и в C#, и в Jave, и в других языках. Хотя в рамках данного руководства мы будем говорить о паттернах в контексте языка C#.

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

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

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

Порождающие паттерны

Порождающие паттерны - это паттерны, которые абстрагируют процесс инстанцирования или, иными словами, процесс порождения классов и объектов. Среди них выделяются следующие:

    Строитель (Builder)

    Прототип (Prototype)

    Одиночка (Singleton)

Другая группа паттернов - структурные паттерны - рассматривает, как классы и объекты образуют более крупные структуры - более сложные по характеру классы и объекты. К таким шаблонам относятся:

    Адаптер (Adapter)

    Мост (Bridge)

    Компоновщик (Composite)

    Декоратор (Decorator)

    Фасад (Facade)

    Приспособленец (Flyweight)

    Заместитель (Proxy)

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

    Команда (Command)

    Интерпретатор (Interpreter)

    Итератор (Iterator)

    Посредник (Mediator)

    Хранитель (Memento)

    Наблюдатель (Observer)

    Состояние (State)

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

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

    Посетитель (Visitor)

Существуют и другие классификации паттернов в зависимости от того, относится паттерн к классам или объектам.

Паттерны классов описывают отношения между классами посредством наследования. Отношения между классами определяются на стадии компиляции. К таким паттернам относятся:

    Фабричный метод (Factory Method)

    Интерпретатор (Interpreter)

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

    Адаптер (Adapter)

Другая часть паттернов - паттерны объектов описывают отношения между объектами. Эти отношения возникают на этапе выполнения, поэтому обладают большей гибкостью. К паттернам объектов относят следующие:

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

    Строитель (Builder)

    Прототип (Prototype)

    Одиночка (Singleton)

    Мост (Bridge)

    Компоновщик (Composite)

    Декоратор (Decorator)

    Фасад (Facade)

    Приспособленец (Flyweight)

    Заместитель (Proxy)

    Цепочка обязанностей (Chain of responsibility)

    Команда (Command)

    Итератор (Iterator)

    Посредник (Mediator)

    Хранитель (Memento)

    Наблюдатель (Observer)

    Состояние (State)

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

    Посетитель (Visitor)

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

И в данном руководстве мы рассмотрим наиболее основные и распространенные паттерны и принципы их использования применительно к языку C#.

Как выбрать нужный паттерн?

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

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

И в конечном счете надо придерживаться принципа KISS (Keep It Simple, Stupid) - сохранять код программы по возможности простым и ясным. Ведь смысл паттернов не в усложнении кода программы, а наоборот в его упрощении.

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

Что такое шаблоны проектирования?

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

Существует три типа шаблонов:

  • структурные;
  • порождающие;
  • поведенческие.

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

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

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

Зачем нужны шаблоны проектирования?

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

Пример

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

Class StrategyAndAdapterExampleClass { private $_class_one; private $_class_two; private $_context; public function __construct($context) { $this->_context = $context; } public function operation1() { if($this->_context == "context_for_class_one") { $this->_class_one->operation1_in_class_one_context(); } else ($this->_context == "context_for_class_two") { $this->_class_two->operation1_in_class_two_context(); } } }

Просто, не правда ли? Давайте посмотрим поближе на шаблон «Стратегия».

Шаблон «Стратегия»

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

В примере выше выбор стратегии основан на значении переменной $context , которое было в момент создания объекта. Если значение было "context_for_class_one" , программа будет использовать класс class_one . И наоборот.

Хорошо, но где это можно использовать?

Представьте, что вы разрабатываете класс, который может создать или обновить запись в базе данных. В обоих случаях входные параметры будут одни и те же (имя, адрес, номер телефона и т. п.), но, в зависимости от ситуации, он будет должен использовать различные функции для обновления и создания записи. Можно каждый раз переписывать условие if/else , а можно создать один метод, который будет принимать контекст:

Class User { public function CreateOrUpdate($name, $address, $mobile, $userid = null) { if(is_null($userid)) { // пользователя не существует, создаем запись } else { // запись есть, обновляем ее } } }

Обычно шаблон «Стратегия» подразумевает инкапсуляцию алгоритмов в классы, но в данном случае это излишне. Помните, что вы не обязаны следовать шаблону слово в слово. Любые варианты допустимы, если они решают задачу и соответствуют концепции.

Шаблон «Адаптер»

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

Также он позволяет изменить некоторые входные данные для совместимости с интерфейсом внутреннего класса.

Как его использовать?

Другое название адаптера - «Обертка». Он «оборачивает» новый интерфейс вокруг класса для его использования. Классический пример: вам надо создать класс предметной модели, имея классы объектов в базе данных. Вместо того, чтобы обращаться к табличным классам напрямую и вызывать их методы по одному, вы можете инкапсулировать вызовы этих методов в одном методе в адаптере. Это не только позволит повторно использовать набор операций, но и избавит вас от постоянного переписывания большого количества кода, если вам потребуется выполнить тот же набор действий в другом месте.

Сравните два примера.

Без адаптера
$user = new User(); $user->CreateOrUpdate(// параметры); $profile = new Profile(); $profile->CreateOrUpdate(// параметры);

Если нам придется использовать такой код повторно, мы будем вынуждены переписывать все это заново.

С использованием адаптера

Мы можем создать класс-обертку Account:

Class Account() { public function NewAccount(// параметры) { $user = new User(); $user->CreateOrUpdate(// часть параметров); $profile = new Profile(); $profile->CreateOrUpdate(// часть параметров); } } $account_domain = new Account(); $account_domain->NewAccount(// параметры);

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

Шаблон «Метод-фабрика»

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

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

Как его использовать?

Фабрика обычно используется для создания различных вариантов базового класса. Допустим, у вас есть класс кнопки - Button - и три варианта - ImageButton , InputButton и FlashButton . С помощью фабрики вы можете создавать различные варианты кнопок в зависимости от ситуации.

Сначала создадим три класса:

Abstract class Button { protected $_html; public function getHtml() { return $this->_html; } } class ImageButton extends Button { protected $_html = "..."; // HTML-код кнопки-картинки } class InputButton extends Button { protected $_html = "..."; // HTML-код обычной кнопки (); } class FlashButton extends Button { protected $_html = "..."; // HTML-код Flash-кнопки }

Теперь мы можем написать нашу фабрику:

Class ButtonFactory { public static function createButton($type) { $baseClass = "Button"; $targetClass = ucfirst($type).$baseClass; if (class_exists($targetClass) && is_subclass_of($targetClass, $baseClass)) { return new $targetClass; } else { throw new Exception("The button type "$type" is not recognized."); } } }

и использовать ее:

$buttons = array("image","input","flash"); foreach($buttons as $b) { echo ButtonFactory::createButton($b)->getHtml() }

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

Шаблон «Декоратор»

Декоратор - это структурный шаблон, который позволяет добавить новое поведение объекту в процессе выполнения программы в зависимости от ситуации.

Цель - в расширении поведения конкретного объекта без необходимости изменять поведение базового класса. Это позволит использовать несколько декораторов одновременно. Этот шаблон - альтернатива наследованию. В отличие от наследования, декоратор добавляет поведение в процессе выполнения программы.

Для реализации декоратора нам понадобится:

  1. Унаследовать класс-декоратор от базового.
  2. Добавить поле со ссылкой на базовый класс в декоратор.
  3. Передать ссылку на декорируемый объект в конструктор декоратора.
  4. Перенаправить методы из декоратора на декорируемый объект.
  5. Переопределить методы в декораторе, поведение которых необходимо изменить.

Как его использовать?

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

Сначала определимся, какие «декорации» нам нужны:

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

Теперь мы можем написать сами декораторы:

Class HtmlLinks { // методы для работы с любой HTML-ссылкой } class LogoutLink extends HtmlLinks { protected $_html; public function __construct() { $this->_html = "Logout"; } public function setHtml($html) { $this->_html = $html; } public function render() { echo $this->_html; } } class LogoutLinkH2Decorator extends HtmlLinks { protected $_logout_link; public function __construct($logout_link) { $this->_logout_link = $logout_link; $this->setHtml("" . $this->_html . ""); } public function __call($name, $args) { $this->_logout_link->$name($args); } } class LogoutLinkUnderlineDecorator extends HtmlLinks { protected $_logout_link; public function __construct($logout_link) { $this->_logout_link = $logout_link; $this->setHtml("" . $this->_html . ""); } public function __call($name, $args) { $this->_logout_link->$name($args); } } class LogoutLinkStrongDecorator extends HtmlLinks { protected $_logout_link; public function __construct($logout_link) { $this->_logout_link = $logout_link; $this->setHtml("" . $this->_html . ""); } public function __call($name, $args) { $this->_logout_link->$name($args); } }

Теперь мы можем использовать их так:

$logout_link = new LogoutLink(); if($is_logged_in) { $logout_link = new LogoutLinkStrongDecorator($logout_link); } if($in_home_page) { $logout_link = new LogoutLinkH2Decorator($logout_link); } else { $logout_link = new LogoutLinkUnderlineDecorator($logout_link); } $logout_link->render();

Обратите внимание, как можно использовать несколько декораторов на одном объекте. Все они используют функцию __call для вызова оригинального метода. Если мы войдем в аккаунт и перейдем на заглавную страницу, результат будет такой:

Logout

Шаблон «Одиночка»

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

Его можно использовать как точку «координации» для других объектов, поскольку поля «Одиночки» будут одинаковы для всех, кто его вызывает.

Как его использовать?

Если вам необходимо передавать определенный экземпляр из класса в класс, вы можете передавать его каждый раз через конструктор или использовать «Одиночку». Допустим, у вас есть класс Session , который содержит данные о текущей сессии. Поскольку сессия инициализируется только один раз, мы можем реализовать его так:

Class Session { private static $instance; public static function getInstance() { if(is_null(self::$instance)) { self::$instance = new self(); } return self::$instance; } private function __construct() { } private function __clone() { } // прочие методы сессии... ... ... } // get a session instance $session = Session::getInstance();

Теперь мы можем получить доступ к сессии из различных участков кода, даже из других классов. Метод getInstance всегда будет возвращать одну и ту же сессию.

Заключение

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

И последнее: при использовании того или иного шаблона убедитесь, что вы решаете задачу правильным способом. Как уже упоминалось, при неправильном использовании шаблоны проектирования могут доставить больше проблем, чем решить. Но при правильном - их пользу нельзя переоценить.

Похожие статьи