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

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

admin
30 июня 2018

Шаблоны проектирования в Python: для стильного кода

Многие шаблоны проектирования встроены в Python из коробки, а другие очень ...