Здесь показаны различия между двумя версиями данной страницы.
| Предыдущая версия справа и слева Предыдущая версия Следующая версия | Предыдущая версия | ||
|
php:shablony_proektirovanija:structural_patterns:decorator [2023/08/17 13:53] werwolf |
php:shablony_proektirovanija:structural_patterns:decorator [2023/08/17 13:58] (текущий) werwolf |
||
|---|---|---|---|
| Строка 1: | Строка 1: | ||
| - | <code>## Декоратор (Decorator) | + | ===== Декоратор (Decorator) ===== |
| - | [▶ code](http://webmastermsk.ru:30000/serg/patterns-php/-/blob/main/structural/Decorator.php) | + | [[http://webmastermsk.ru:30000/serg/patterns-php/-/blob/main/structural/Decorator.php|▶ code]] |
| > Шаблон декоратора позволяет динамически изменять поведение объекта во время выполнения, заключая его в объект класса | > Шаблон декоратора позволяет динамически изменять поведение объекта во время выполнения, заключая его в объект класса | ||
| - | декоратора. | + | > декоратора. |
| - | Возможность динамически добавлять новые обязанности, не прибегая к порождению подклассов, облегчает понимание основных | + | Возможность динамически добавлять новые обязанности, не прибегая к порождению подклассов, облегчает понимание основных\\ |
| обязанностей объектов и не запутывает основную бизнес-логику информационных систем. | обязанностей объектов и не запутывает основную бизнес-логику информационных систем. | ||
| - | Можно добавлять и удалять обязанности во время выполнения программы, в то время как при использовании наследования надо | + | Можно добавлять и удалять обязанности во время выполнения программы, в то время как при использовании наследования надо\\ |
| было бы создавать новый класс для каждой дополнительной обязанности. | было бы создавать новый класс для каждой дополнительной обязанности. | ||
| - | Данный шаблон позволяет не создавать классов, перегруженных методами. Новые обязанности можно добавлять только по мере | + | Данный шаблон позволяет не создавать классов, перегруженных методами. Новые обязанности можно добавлять только по мере\\ |
| необходимости, не перегружая программное обеспечение лишними классами. | необходимости, не перегружая программное обеспечение лишними классами. | ||
| **Использование:** | **Использование:** | ||
| - | - Когда вам нужно добавлять обязанности объектам на лету, незаметно для кода, который их использует. | + | * Когда вам нужно добавлять обязанности объектам на лету, незаметно для кода, который их использует. |
| - | - Объекты помещают в обёртки, имеющие дополнительные поведения. Обёртки и сами объекты имеют одинаковый интерфейс, | + | * Объекты помещают в обёртки, имеющие дополнительные поведения. Обёртки и сами объекты имеют одинаковый интерфейс,\\ |
| - | поэтому клиентам без разницы, с чем работать – с обычным объектом данных или с обёрнутым. | + | поэтому клиентам без разницы, с чем работать – с обычным объектом данных или с обёрнутым. |
| - | - Когда нельзя расширить обязанности объекта с помощью наследования. | + | * Когда нельзя расширить обязанности объекта с помощью наследования. |
| - | - Во многих языках программирования есть ключевое слово final, которое может заблокировать наследование класса. | + | * Во многих языках программирования есть ключевое слово final, которое может заблокировать наследование класса.\\ |
| - | Расширить такие классы можно только с помощью Декоратора. | + | Расширить такие классы можно только с помощью Декоратора. |
| **Преимущества:** | **Преимущества:** | ||
| - | - Большая гибкость, чем у наследования. | + | * Большая гибкость, чем у наследования. |
| - | - Позволяет добавлять обязанности на лету. | + | * Позволяет добавлять обязанности на лету. |
| - | - Можно добавлять несколько новых обязанностей сразу. | + | * Можно добавлять несколько новых обязанностей сразу. |
| - | - Позволяет иметь несколько мелких объектов вместо одного объекта на все случаи жизни. | + | * Позволяет иметь несколько мелких объектов вместо одного объекта на все случаи жизни. |
| **Недостатки:** | **Недостатки:** | ||
| - | - Трудно конфигурировать многократно обёрнутые объекты. | + | * Трудно конфигурировать многократно обёрнутые объекты. |
| - | - Обилие крошечных классов. | + | * Обилие крошечных классов. |
| - | <details> | ||
| - | <summary><strong>Связи с другими паттернами</strong></summary> | ||
| - | <br> | ||
| - | Адаптер меняет интерфейс существующего объекта. Декоратор улучшает другой объект без изменения его интерфейса. Причём | ||
| - | Декоратор поддерживает рекурсивную вложенность, чего не скажешь об Адаптере.<br> | ||
| - | <br> | ||
| - | Адаптер предоставляет классу альтернативный интерфейс. Декоратор предоставляет расширенный интерфейс. Заместитель | ||
| - | предоставляет тот же интерфейс.<br> | ||
| - | <br> | ||
| - | Цепочка обязанностей и Декоратор имеют очень похожие структуры. Оба паттерна базируются на принципе рекурсивного | ||
| - | выполнения операции через серию связанных объектов. Но есть и несколько важных отличий.<br> | ||
| - | <br> | ||
| - | Обработчики в Цепочке обязанностей могут выполнять произвольные действия, независимые друг от друга, а также в любой | ||
| - | момент прерывать дальнейшую передачу по цепочке. С другой стороны Декораторы расширяют какое-то определённое действие, | ||
| - | не ломая интерфейс базовой операции и не прерывая выполнение остальных декораторов.<br> | ||
| - | <br> | ||
| - | Компоновщик и Декоратор имеют похожие структуры классов из-за того, что оба построены на рекурсивной вложенности. Она | ||
| - | позволяет связать в одну структуру бесконечное количество объектов.<br> | ||
| - | <br> | ||
| - | Декоратор оборачивает только один объект, а узел Компоновщика может иметь много детей. Декоратор добавляет вложенному | ||
| - | объекту новую функциональность, а Компоновщик не добавляет ничего нового, но «суммирует» результаты всех своих детей.<br> | ||
| - | <br> | ||
| - | Но они могут и сотрудничать: Компоновщик может использовать Декоратор, чтобы переопределить функции отдельных частей | ||
| - | дерева компонентов.<br> | ||
| - | <br> | ||
| - | Архитектура, построенная на Компоновщиках и Декораторах, часто может быть улучшена за счёт внедрения Прототипа. Он | ||
| - | позволяет клонировать сложные структуры объектов, а не собирать их заново.<br> | ||
| - | <br> | ||
| - | Стратегия меняет поведение объекта «изнутри», а Декоратор изменяет его «снаружи».<br> | ||
| - | <br> | ||
| - | Декоратор и Заместитель имеют схожие структуры, но разные назначения. Они похожи тем, что оба построены на принципе | ||
| - | композиции и делегируют работу другим объектам. Паттерны отличаются тем, что Заместитель сам управляет жизнью сервисного | ||
| - | объекта, а обёртывание Декораторов контролируется клиентом.<br> | ||
| - | <br> | ||
| - | </details> | ||
| + | **Связи с другими паттернами** | ||
| + | |||
| + | Адаптер меняет интерфейс существующего объекта. Декоратор улучшает другой объект без изменения его интерфейса. Причём\\ | ||
| + | Декоратор поддерживает рекурсивную вложенность, чего не скажешь об Адаптере. | ||
| + | |||
| + | Адаптер предоставляет классу альтернативный интерфейс. Декоратор предоставляет расширенный интерфейс. Заместитель\\ | ||
| + | предоставляет тот же интерфейс. | ||
| + | |||
| + | Цепочка обязанностей и Декоратор имеют очень похожие структуры. Оба паттерна базируются на принципе рекурсивного\\ | ||
| + | выполнения операции через серию связанных объектов. Но есть и несколько важных отличий. | ||
| + | |||
| + | Обработчики в Цепочке обязанностей могут выполнять произвольные действия, независимые друг от друга, а также в любой\\ | ||
| + | момент прерывать дальнейшую передачу по цепочке. С другой стороны Декораторы расширяют какое-то определённое действие,\\ | ||
| + | не ломая интерфейс базовой операции и не прерывая выполнение остальных декораторов. | ||
| + | |||
| + | Компоновщик и Декоратор имеют похожие структуры классов из-за того, что оба построены на рекурсивной вложенности. Она\\ | ||
| + | позволяет связать в одну структуру бесконечное количество объектов. | ||
| + | |||
| + | Декоратор оборачивает только один объект, а узел Компоновщика может иметь много детей. Декоратор добавляет вложенному\\ | ||
| + | объекту новую функциональность, а Компоновщик не добавляет ничего нового, но «суммирует» результаты всех своих детей. | ||
| + | |||
| + | Но они могут и сотрудничать: Компоновщик может использовать Декоратор, чтобы переопределить функции отдельных частей\\ | ||
| + | дерева компонентов. | ||
| + | |||
| + | Архитектура, построенная на Компоновщиках и Декораторах, часто может быть улучшена за счёт внедрения Прототипа. Он\\ | ||
| + | позволяет клонировать сложные структуры объектов, а не собирать их заново. | ||
| + | |||
| + | Стратегия меняет поведение объекта «изнутри», а Декоратор изменяет его «снаружи». | ||
| + | |||
| + | Декоратор и Заместитель имеют схожие структуры, но разные назначения. Они похожи тем, что оба построены на принципе\\ | ||
| + | композиции и делегируют работу другим объектам. Паттерны отличаются тем, что Заместитель сам управляет жизнью сервисного\\ | ||
| + | объекта, а обёртывание Декораторов контролируется клиентом. | ||
| + | |||
| + | |||
| + | <code php> | ||
| + | |||
| + | <?php | ||
| + | |||
| + | interface Worker | ||
| + | { | ||
| + | public function countSalary(): int; | ||
| + | } | ||
| + | |||
| + | abstract class WorkerDecorator implements Worker | ||
| + | { | ||
| + | public Worker $worker; | ||
| + | |||
| + | public function __construct(Worker $worker) | ||
| + | { | ||
| + | $this->worker = $worker; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | class Developer implements Worker | ||
| + | { | ||
| + | public function countSalary(): int | ||
| + | { | ||
| + | return 20 * 3000; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | class DeveloperOverTime extends WorkerDecorator | ||
| + | { | ||
| + | public function countSalary(): int | ||
| + | { | ||
| + | return $this->worker->countSalary() + $this->worker->countSalary() * 0.2; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | $developer = new Developer(); | ||
| + | $developerOverTime = new DeveloperOverTime($developer); | ||
| + | |||
| + | echo $developer->countSalary(); // 60000 | ||
| + | echo $developerOverTime->countSalary(); // 72000 | ||
| + | |||
| + | // ----------- 2 ----------- | ||
| + | |||
| + | interface Coffee | ||
| + | { | ||
| + | public function getCost(); | ||
| + | |||
| + | public function getDescription(); | ||
| + | } | ||
| + | |||
| + | class SimpleCoffee implements Coffee | ||
| + | { | ||
| + | public function getCost() | ||
| + | { | ||
| + | return 10; | ||
| + | } | ||
| + | |||
| + | public function getDescription() | ||
| + | { | ||
| + | return 'Simple coffee'; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | class MilkCoffee implements Coffee | ||
| + | { | ||
| + | protected $coffee; | ||
| + | |||
| + | public function __construct(Coffee $coffee) | ||
| + | { | ||
| + | $this->coffee = $coffee; | ||
| + | } | ||
| + | |||
| + | public function getCost() | ||
| + | { | ||
| + | return $this->coffee->getCost() + 2; | ||
| + | } | ||
| + | |||
| + | public function getDescription() | ||
| + | { | ||
| + | return $this->coffee->getDescription() . ', milk'; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | class WhipCoffee implements Coffee | ||
| + | { | ||
| + | protected $coffee; | ||
| + | |||
| + | public function __construct(Coffee $coffee) | ||
| + | { | ||
| + | $this->coffee = $coffee; | ||
| + | } | ||
| + | |||
| + | public function getCost() | ||
| + | { | ||
| + | return $this->coffee->getCost() + 5; | ||
| + | } | ||
| + | |||
| + | public function getDescription() | ||
| + | { | ||
| + | return $this->coffee->getDescription() . ', whip'; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | $someCoffee = new SimpleCoffee(); | ||
| + | echo $someCoffee->getCost(); // 10 | ||
| + | echo $someCoffee->getDescription(); // Simple Coffee | ||
| + | |||
| + | $someCoffee = new MilkCoffee($someCoffee); | ||
| + | echo $someCoffee->getCost(); // 12 | ||
| + | echo $someCoffee->getDescription(); // Simple Coffee, milk | ||
| + | |||
| + | $someCoffee = new WhipCoffee($someCoffee); | ||
| + | echo $someCoffee->getCost(); // 17 | ||
| + | echo $someCoffee->getDescription(); // Simple Coffee, milk, whip | ||
| + | |||
| + | |||
| + | // ----------- 3 ----------- | ||
| + | |||
| + | interface Booking | ||
| + | { | ||
| + | public function calculatePrice(): int; | ||
| + | |||
| + | public function getDescription(): string; | ||
| + | } | ||
| + | |||
| + | // Шаблон "Декоратор" хранит ссылку на объект (компонент), определяет его интерфейс и переадресует на него рабочие запросы. | ||
| + | abstract class BookingDecorator implements Booking | ||
| + | { | ||
| + | public function __construct(protected Booking $booking) | ||
| + | { | ||
| + | } | ||
| + | } | ||
| + | |||
| + | // Системный компонент определяет интерфейс объекта, на который могут быть динамически возложены дополнительные обязанности | ||
| + | class DoubleRoomBooking implements Booking | ||
| + | { | ||
| + | public function calculatePrice(): int | ||
| + | { | ||
| + | return 40; | ||
| + | } | ||
| + | |||
| + | public function getDescription(): string | ||
| + | { | ||
| + | return 'double room'; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | // Конкретный компонент определяет объект, на который возлагаются дополнительные обязанности. | ||
| + | class ExtraBed extends BookingDecorator | ||
| + | { | ||
| + | private const PRICE = 30; | ||
| + | |||
| + | public function calculatePrice(): int | ||
| + | { | ||
| + | return $this->booking->calculatePrice() + self::PRICE; | ||
| + | } | ||
| + | |||
| + | public function getDescription(): string | ||
| + | { | ||
| + | return $this->booking->getDescription() . ' with extra bed'; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | class WiFi extends BookingDecorator | ||
| + | { | ||
| + | private const PRICE = 2; | ||
| + | |||
| + | public function calculatePrice(): int | ||
| + | { | ||
| + | return $this->booking->calculatePrice() + self::PRICE; | ||
| + | } | ||
| + | |||
| + | public function getDescription(): string | ||
| + | { | ||
| + | return $this->booking->getDescription() . ' with wifi'; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | $booking = new DoubleRoomBooking(); | ||
| + | |||
| + | echo $booking->calculatePrice(); // 4 | ||
| + | echo $booking->getDescription(); // double room | ||
| + | |||
| + | $booking2 = new DoubleRoomBooking(); | ||
| + | $booking2 = new WiFi($booking2); | ||
| + | |||
| + | echo $booking2->calculatePrice(); // 42 | ||
| + | echo $booking2->getDescription(); // double room with wifi | ||
| + | |||
| + | $booking3 = new DoubleRoomBooking(); | ||
| + | $booking3 = new WiFi($booking); | ||
| + | $booking3 = new ExtraBed($booking); | ||
| + | |||
| + | echo $booking3->calculatePrice(); // 72 | ||
| + | echo $booking3->getDescription(); // double room with wifi with extra bed | ||
| </code> | </code> | ||