Шаблоны проектирования по-человечески: 6 порождающих паттернов, которые упростят жизнь
Ультра-простое объяснение шаблонов проектирования, известных как порождающие паттерны. Они решают уйму проблем, так почему бы не рассмотреть их детальнее?
Осторожно!
- шаблоны проектирования – не панацея от всех бед;
- не пытайтесь переусердствовать, в противном случае решение проблем превратится в первопричину этих проблем;
- использовать паттерны проектирования нужно в правильном месте и в правильном порядке.
Основная классификация
Каждый из типов рассчитан на конкретный круг задач, а делятся паттерны на:
- Порождающие паттерны.
- Структурные.
- Поведенческие.
Порождающие паттерны
Этот тип особенно важен, когда система зависит не столько от наследования классов, сколько от композиции. Порождающие паттерны отвечают за создание объектов и позволяют системе быть независимой от типов этих самых объектов и от процесса порождения.
В свою очередь, порождающие паттерны делятся на:
- Simple Factory
- Factory Method
- Abstract Factory
- Builder
- Prototype
- Singleton
1. Паттерн Simple Factory
Предположим, вы строите дом, и вам необходим проход. Было бы глупо всякий раз, когда нужна дверь, облачаться в одежду плотника, чтобы мастерить ее. Вместо этого вы получаете дверь с «завода».
Паттерн предназначен для инкапсуляции процесса образования объектов с помощью отдельного класса. «Простая Фабрика» удобна, но за простоту приходится платить: привязка к конкретной реализации исключает гибкость системы. Simple Factory следует использовать только там, где архитектура не будет изменяться.
Допустим, у нас есть интерфейс двери:
interface Door { public function getWidth(): float; public function getHeight(): float; } class WoodenDoor implements Door { protected $width; protected $height; public function __construct(float $width, float $height) { $this->width = $width; $this->height = $height; } public function getWidth(): float { return $this->width; } public function getHeight(): float { return $this->height; } }
Далее появляется завод, который изготавливает дверь и возвращает ее нам:
class DoorFactory { public static function makeDoor($width, $height): Door { return new WoodenDoor($width, $height); } }
И только после этого мы можем воспользоваться нашей дверью:
$door = DoorFactory::makeDoor(100, 200); echo 'Ширина: ' . $door->getWidth(); echo 'Высота: ' . $door->getHeight();
2. Паттерн Factory Method
Но порождающие паттерны на этом не заканчиваются. Шаблон проектирования Factory Method работает с полиморфизмом. В главном классе задается интерфейс, а реализация определяется уже подклассами.
Допустим, у нас есть интерфейс соискателя:
interface Interviewer { public function askQuestions(); } class Developer implements Interviewer { public function askQuestions() { echo 'Спросить о шаблонах проектирования'; } } class CommunityExecutive implements Interviewer { public function askQuestions() { echo 'Спросить об общественном строительстве'; } }
Теперь создаем менеджера по подбору персонала:
abstract class HiringManager { // Factory method abstract public function makeInterviewer(): Interviewer; public function takeInterview() { $interviewer = $this->makeInterviewer(); $interviewer->askQuestions(); } }
Предоставляем необходимого соискателя:
class DevelopmentManager extends HiringManager { public function makeInterviewer(): Interviewer { return new Developer(); } } class MarketingManager extends HiringManager { public function makeInterviewer(): Interviewer { return new CommunityExecutive(); } }
После чего можно использовать:
$devManager = new DevelopmentManager(); $devManager->takeInterview(); $marketingManager = new MarketingManager(); $marketingManager->takeInterview();
3. Паттерн Abstract Factory
Вернемся к примеру из Simple Factory. Может понадобиться деревянная дверь, металлическая или пластиковая. Разные типы дверей поставляются из разных магазинов, да и специалисты должны быть соответствующие: плотник, сварщик и т. д. Нам нужна «Абстрактная Фабрика», которая объединяет разные, но связанные фабрики без указания их конкретных классов.
Есть интерфейс двери и некоторые этапы реализации для нее :
interface Door { public function getDescription(); } class WoodenDoor implements Door { public function getDescription() { echo 'Я деревянная дверь'; } } class IronDoor implements Door { public function getDescription() { echo 'Я железная дверь'; } }
Получаем экспертов для каждого типа дверей:
interface DoorFittingExpert { public function getDescription(); } class Welder implements DoorFittingExpert { public function getDescription() { echo 'Я могу подобрать только железные двери'; } } class Carpenter implements DoorFittingExpert { public function getDescription() { echo 'Я могу подобрать только деревянные двери'; } }
Имеем ту самую Abstract Factory для создания семейства объектов:
interface DoorFactory { public function makeDoor(): Door; public function makeFittingExpert(): DoorFittingExpert; } // Завод по работе с деревом и плотник class WoodenDoorFactory implements DoorFactory { public function makeDoor(): Door { return new WoodenDoor(); } public function makeFittingExpert(): DoorFittingExpert { return new Carpenter(); } } // Завод по производству железных дверей и сварщик class IronDoorFactory implements DoorFactory { public function makeDoor(): Door { return new IronDoor(); } public function makeFittingExpert(): DoorFittingExpert { return new Welder(); } }
Используем:
$woodenFactory = new WoodenDoorFactory(); $door = $woodenFactory->makeDoor(); $expert = $woodenFactory->makeFittingExpert(); $door->getDescription(); // На выходе: я деревянная дверь $expert->getDescription(); // На выходе: я могу установить только деревянную дверь // То же для завода по изготовлению железных дверей $ironFactory = new IronDoorFactory(); $door = $ironFactory->makeDoor(); $expert = $ironFactory->makeFittingExpert(); $door->getDescription(); // На выходе: я железная дверь $expert->getDescription(); // На выходе: я могу установить только железную дверь
4. Паттерн Builder
Вы зашли в ресторан быстрого питания, заказали гамбургер, и вам его приготовили. Но представьте, что нужно индивидуальное приготовление с определенным хлебом, соусом, сыром и т. д. Здесь поможет шаблон «Строитель», который отвечает за процесс поэтапного создания объекта.
У нас есть желаемый гамбургер:
class Burger { protected $size; protected $cheese = false; protected $pepperoni = false; protected $lettuce = false; protected $tomato = false; public function __construct(BurgerBuilder $builder) { $this->size = $builder->size; $this->cheese = $builder->cheese; $this->pepperoni = $builder->pepperoni; $this->lettuce = $builder->lettuce; $this->tomato = $builder->tomato; } }
Применяем конструирование:
class BurgerBuilder { public $size; public $cheese = false; public $pepperoni = false; public $lettuce = false; public $tomato = false; public function __construct(int $size) { $this->size = $size; } public function addPepperoni() { $this->pepperoni = true; return $this; } public function addLettuce() { $this->lettuce = true; return $this; } public function addCheese() { $this->cheese = true; return $this; } public function addTomato() { $this->tomato = true; return $this; } public function build(): Burger { return new Burger($this); } }
Используем:
$burger = (new BurgerBuilder(14)) ->addPepperoni() ->addLettuce() ->addTomato() ->build();
5. Паттерн Prototype
Помните Долли? Овцу, которую клонировали. Порождающие паттерны «Прототип» - это именно о клонировании.
В PHP это легко реализовать, используя clone:
class Sheep { protected $name; protected $category; public function __construct(string $name, string $category = 'Горная овца') { $this->name = $name; $this->category = $category; } public function setName(string $name) { $this->name = $name; } public function getName() { return $this->name; } public function setCategory(string $category) { $this->category = $category; } public function getCategory() { return $this->category; } }
После можно приступать к клонированию, как показано ниже:
$original = new Sheep('Джолли'); echo $original->getName(); // Джолли echo $original->getCategory(); // Горная овца // Clone and modify what is required $cloned = clone $original; $cloned->setName('Долли'); echo $cloned->getName(); // Долли echo $cloned->getCategory(); // Горная овца
6. Паттерн Singleton
Президент может быть только один, и именно он привлекается к действию, когда это необходимо. Президент – одиночка, и по аналогии наш паттерн контролирует создание лишь одного экземпляра класса.
Сделайте конструктор закрытым, отключите клонирование, расширение и создайте статическую переменную для экземпляра:
final class President { private static $instance; private function __construct() { // Прячем конструктор } public static function getInstance(): President { if (!self::$instance) { self::$instance = new self(); } return self::$instance; } private function __clone() { // Отключаем клонирование } private function __wakeup() { // Отключаем расширение } }
Используем:
$president1 = President::getInstance(); $president2 = President::getInstance(); var_dump($president1 === $president2); // истина
Также рекомендуем Вам посмотреть:
Шаблоны проектирования по-человечески: структурные паттерны
Шаблоны проектирования по-человечески: поведенческие паттерны в примерах
Лучший видеокурс по шаблонам проектирования
4 лучших книг о шаблонах проектирования
20 полезных навыков, которые можно освоить за 3 дня