You are on page 1of 47

Архитектура, алгоритмы и паттерны на PHP

Урок 5.
Структурные шаблоны
На уроке разберём

● Структурные шаблоны: общие характеристики.


● Какие шаблоны можно отнести к структурным?
● Шаблоны Adapter, Composite, Decorator, Facade.
В структурных шаблонах
рассматривается вопрос о том,
как из классов и объектов
образуются более крупные
структуры.
Список шаблонов (GoF)

Adapter Bridge Composite

Decorator Facade Flyweight Proxy


Нам наверняка пригодятся

Adapter Bridge Composite

Decorator Facade Flyweight Proxy


Адаптер (Adapter)

Преобразует интерфейс одного


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

Создание класса-обёртки с необходимым интерфейсом.


Адаптер: пример

Имеем: Статья, которой


можно делиться в
соцсетях. API соцсетей
различны.

Хотим: иметь единый


интерфейс для
публикации в соцсетях.
interface IPublisher
{
public function publisher(string $content): void;
}

class TwitterAdapter implements IPublisher


{
private $twitter;
public function __construct(Twitter $twitter)
{
$this->twitter = $twitter;
}
public function publisher(string $content): void
{ Создаём классы для
}
$this->twitter>sendTweet($content);
публикации в соцсетях с
} единым интерфейсом
class FacebookAdapter implements IPublisher
{
private $facebook;
public function __construct(Facebook $facebook)
{
$this->facebook = $facebook;
}
public function publisher(string $content): void
{
$this->facebook>publish($content, new DateTime());
}
}
public function testAdapter(string $newsContent)
{
$facebookAdapter = new FacebookAdapter(new Facebook());
$facebookAdapter->publish($newsContent); Пример использования
$twitterAdapter = new TwitterAdapter(new Twitter());
$twitterAdapter->publish($newsContent);
}
Когда стоит применять

● Хотите использовать существующий класс, но его интерфейс не


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

● Приводит класс к отличному от своего интерфейсу.


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

● При слишком сильном отличии адаптируемого класса от


целевого интерфейса процесс адаптации может отнять много
сил и времени.
Компоновщик (Composite)

Когда нужно поддерживать иерархию


объектов с единым интерфейсом.
UML-диаграмма
Основная цель Компоновщика

Описывает, как можно применить рекурсивную композицию


таким образом, чтобы клиенту не пришлось различать
простые и составные объекты.
Компонент (Component)

● Объявляет интерфейс для компонуемых объектов.

● Предоставляет подходящую реализацию операций по умолчанию, общую для


всех классов.

● Объявляет интерфейс для доступа к потомкам и управления ими.

● Определяет интерфейс для доступа к родителю компонента в рекурсивной


структуре и при необходимости реализует его. Эта возможность необязательна.
Лист (Leaf)

● Представляет листовые узлы и не имеет потомков.


● Определяет поведение примитивных объектов.
● Агрегат, контейнер, композит (Composite).
● Определяет поведение компонентов, у которых есть потомки.
● Хранит компоненты-потомки.
● Реализует относящиеся к управлению потомками операции в интерфейсе
класса Component.
Компоновщик: пример
Имеем: классы
abstract class Renderable
{ Страницы, Новость и
abstract public function render(): string;
Комментарий, которые
public function addElement(IRenderable $element)
{ преобразуются в HTML.
throw new RuntimeException('Реализуй метод для его использования');
}

public function removeElement(IRenderable $element)


Хотим: создавать
{ иерархию классов,
throw new RuntimeException('Реализуй метод для его использования');
} чтобы сразу
}
генерировать готовую
страницу.
class NewsPage extends Renderable
{
private $elements;

public function render(): string


{
$blockCode = '<div>';

foreach ($this->elements as $element) {


$blockCode .= $element->render();
} Компоновщик должен
$blockCode .= '</div>'; реализовывать тот
return $blockCode; же интерфейс, что и
}
объекты, которые он
public function addElement(Renderable $element): void
{ компонует.
$this->elements[] = $element;
}

public function removeElement(Renderable $element): void


{
echo 'Удаление агрегата или листа';
}
}
class News extends Renderable
{
public function __construct(string $text)
{
$this->text = $text;
}

public function render(): string


{
echo 'Преобразование разметки Новости в HTML';
}
}
Компонуемые объекты:
class Comments extends Renderable
{ Новость и Комментарий.
public function __construct(string $text)
{
$this->text = $text;
}

public function render(): string


{
echo 'Преобразование разметки Комментариев в HTML';
}
}
public function testComposite(string $newsText, array $comments)
{
$newsPage = new NewsPage();
$newsPage->addElement(new News($newsText));

/* @var string[] $comments Массив объектов Comment */


Пример использования:
foreach ($comments as $comment) {
$newsPage->addElement(new Comment($comment));
генерация страницы.
}

$newsPage->render();
}
Когда стоит применять

● Нужно представить иерархию объектов вида «часть — целое».


● Хотите, чтобы клиенты единообразно трактовали составные и
индивидуальные объекты.
Плюсы паттерна

● Определяет иерархии классов, состоящие из примитивных и


составных объектов.
● Упрощает архитектуру клиента.
● Облегчает добавление новых видов компонентов.
Минусы паттерна

● Слишком общий дизайн классов, что накладывает ограничения


на состав композиции
Декоратор (Decorator)

Динамически добавляет объекту


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

Динамически добавляет дополнительные поведения или


обязанности объекту.
Декоратор (Decorator): пример

Имеем: текст в разметке


Markdown для
преобразования в HTML,
XML и другие разметки.

Хотим: генерировать HTML


через единый интерфейс.
interface IMarkdown
{
public function render(): string;
}

class HtmlRender implements IMarkdown


{
private $text;

public function __construct(string $text) Класс рендера в HTML-


{
$this->text = $text; представлении.
}

public function render(): string


{
return htmlspecialchars($this->text);
}
}
abstract class Decorator implements IMarkdown
{
protected $content = null;

public function __construct(IMarkdown $content)


{
$this->content = $content;
}
}

class Bold extends Decorator


{
public function render(): string
{ Декораторы
return '<b>' . str_replace('**', '', $this->content->render()) . '</b>';
}
}

class Header4 extends Decorator


{
public function render(): string
{
return '<h4>' . str_replace('####', '', $this->content->render()) . '</h4>';
}
}
public function testDecorator(string $text)
{
$htmlRender =
new Bold(
new Header4( Пример использования
new HtmlRender($text)
)
);
$htmlRender->render();
}
Плюсы паттерна
● Гибкая альтернатива наследованию.
● Можно выполнить одну и ту же обязанность два раза.
● Декораторы могут вкладываться друг в друга и образовывать любое
число обязанностей.
● Позволяет расширять поведение и обязанности класса во время
выполнения программы и не иметь ненужной функциональности в
классе.
● Возможность произвольно сочетать дополнительную функциональность
в любой момент.
Минусы паттерна

● Множество мелких объектов, в функциональности которых нужно


разобраться, прежде чем начать их применять.

● На изучение и отлаживание может уйти много сил и времени.


Фасад (Facade)

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

Скрывает сложность системы, сводя все возможные внешние вызовы


к одному объекту.
Фасад: пример
Пример: оформление заказа в интернет-магазине.

Этапы:
● рассчитать стоимость товаров;
● рассчитать скидку;
● деактивировать промокод (если он использовался);
● зарезервировать товары на складе, сформировать заказ;
● отправить письмо с подтверждением пользователю.
class Order
{
public function getUser(): User { echo 'Получаю данные по пользователю'; }
public function getAddress(): string { echo 'Получаю адрес доставки'; }
public function getItems(): array { echo 'Получаю все позиции в заказе'; }
public function getTotalPrice(): float { echo 'Получаю суммарную стоимость заказа'; }
public function assertCheck(): void { echo 'Проверяю корректность всех данных заказа'; }
}

class Delivery
{
public function getDeliveryPrice(string $address): float { echo 'Получаю стоимость доставки'; }
}

class PromoCode Используемые


{
public function getDiscount(): float { echo 'Получаю скидку'; } классы
public function deactivate(): void { echo 'Диактивирую промокод'; }
}

class Warehouse
{
public function holdItem(Item $item): void { echo 'Замораживаю деньги по карте пользователя'; }
}

class Mailer
{
public function sendMail(User $to): bool { echo 'Отправляю письмо пользователю'; }
}
class OrderFacade
{
private $order;
private $delivery;
private $promocode;
private $warehouse;
private $mailer;

public function __construct(Order $order, Delivery $delivery, Warehouse $warehouse, Mailer $mailer)
{
$this->order = $order;
$this->delivery = $delivery;
$this->warehouse = $warehouse;
$this->mailer = $mailer;
}
public function setPromocode(Promocode $promocode)
{
$this->promocode = $promocode;
}
public function checkout()
{
$total = $this->order->getTotalPrice();

if ($this->promocode !== null) {


$total *= (1.0 - $this->promocode->getDiscount() / 100.00);
$this->promocode->deactivate();
}

$total += $this->delivery->getPrice($this->order->getAddress());
foreach ($this->order->getItems() as $item) {
$this->warehouse->holdItem($item);
}

$check = $this->order->assertCheck();
$this->mailer->sendMail($this->order->getUser()); } }
Когда стоит применять

● Нужно предоставить простой интерфейс к сложной подсистеме.


● Между клиентами и классами реализации абстракции много
зависимостей.
● Если хотите разделить систему на отдельные слои.
Плюсы паттерна

● Инкапсулирует компоненты системы от клиентов.


● Удовлетворяет принципу низкой связанности (low coupling).
● Помогает разделять систему на слои.
● Не мешает напрямую обращаться к подсистеме классов.
Минусы паттерна

● Если сам Фасад содержит слишком много бизнес-логики, он


превращается в спагетти-код.
Задание
1. Реализовать на PHP пример Декоратора,
позволяющий отправлять уведомления несколькими
различными способами (описан в этой методичке).
2. Реализовать паттерн Адаптер для связи внешней
библиотеки (классы SquareAreaLib и CircleAreaLib)
вычисления площади квадрата (getSquareArea) и
площади круга (getCircleArea) с интерфейсами
ISquare и ICircle имеющегося кода. Примеры классов
даны ниже. Причём во внешней библиотеке
используются для расчётов формулы нахождения
через диагонали фигур, а в интерфейсах квадрата и
круга — формулы, принимающие значения одной
стороны и длины окружности соответственно.
Задание:
Внешняя библиотека:
<?php
class CircleAreaLib
{
public function getCircleArea(int $diagonal)
{
$area = (M_PI * $diagonal**2))/4;

return $area;
}
}

class SquareAreaLib
{
public function getSquareArea(int $diagonal)
{
$area = ($diagonal**2)/2;

return $area;
}
}
Задание:
Имеющиеся интерфейсы:
<?php
interface ISquare
{
function squareArea(int $sideSquare);
}

interface ICircle
{
function circleArea(int $circumference);
}

You might also like