Шаблоны проектирования по-человечески: 6 порождающих паттернов, которые упростят жизнь

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

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

Осторожно!

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

Основная классификация

Каждый из типов рассчитан на конкретный круг задач, а делятся паттерны на:

  1. Порождающие паттерны.
  2. Структурные.
  3. Поведенческие.

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

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

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

В свою очередь, порождающие паттерны делятся на:

  1. Simple Factory
  2. Factory Method
  3. Abstract Factory
  4. Builder
  5. Prototype
  6. 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 дня

МЕРОПРИЯТИЯ

Комментарии

ВАКАНСИИ

Добавить вакансию
Golang разработчик (middle)
от 230000 RUB до 300000 RUB
DevOps
Санкт-Петербург, от 150000 RUB до 400000 RUB

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ