Шаблоны проектирования по-человечески: 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 дня