Шаблони проектування (Паттерни). Приклади на PHP

Патерни (або шаблони) проектування описують типові способи вирішення поширених проблем при проектуванні програм.

Види шаблонів проектування

TODO добавить ссылки

  • Породжувальні
    • Проста фабрика
  • Структурні
  • Поведінкові

Породжувальні паттерни проектування

Породжувальні паттерни описують створення (instantiate) обєкуту або групу зв'язаних обєктів, інакшими словами відповідають за зручне та безпечне створення нових об’єктів або навіть цілих сімейств об’єктів.

Wiki: Породжуючі шаблони (англ. Creational patterns) — це шаблони проектування, що абстрагують процес побудови об'єктів. Вони допоможуть зробити систему незалежною від способу створення, композиції та представлення її об'єктів.

  • Проста фабрика
  • Фабричний метод

Паттерн "Проста фабрика"

Проста фабрика просто генерить екземпляр для клієнта без представлення якоїн-небудь логіки самого екземпляра

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 'Width: ' . $door->getWidth();
echo 'Height: ' . $door->getHeight();

Коли використовувати?

Коли створення об'єкта має на увазі якусь логіку, а не просто кілька присвоювань, то має сенс делегувати завдання виділеній фабриці, а не повторювати всюди один і той же код.

Паттерн: "Фабричний метод"

Це спосіб делегування логіки створення обєкта (instantiation logic) дочірнім класам. Іншими словами фабричний метод — це породжувальний патерн проектування, який визначає загальний інтерфейс для створення об’єктів у суперкласі, дозволяючи підкласам змінювати тип створюваних об’єктів.

interface Interviewer
{
    public function askQuestions();
}

class Developer implements Interviewer
{
    public function askQuestions()
    {
        echo 'Asking about design patterns!';
    }
}

class CommunityExecutive implements Interviewer
{
    public function askQuestions()
    {
        echo 'Asking about community building';
    }
}

abstract class HiringManager
{

    // Фабричный метод
    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(); // Output: Asking about design patterns!

 $marketingManager = new MarketingManager();
 $marketingManager->takeInterview(); // Output: Asking about community building

Коли використовувати?

Цей шаблон корисний для якихось загальних обробок в класі, але необхідні підкласи динамічно визначаються в ході виконання (runtime). Тобто коли клієнт не знає, який саме підклас може йому знадобитися.

Паттерн: "Абстрактна фабрика"

Це фабрика фабрик. Тобто фабрика, групує індивідуальні, але взаємопов'язані / взаємозалежні фабрики без вказівки для них конкретних класів.

Wiki: Шаблон «Абстрактна фабрика» описує спосіб інкапсулювання групи індивідуальних фабрик, об'єднаних якоюсь темою, без вказівки для них конкретних класів.

Приклад

Створення обєктів дверей

interface Door
{
    public function getDescription();
}

class WoodenDoor implements Door
{
    public function getDescription()
    {
        echo 'I am a wooden door';
    }
}

class IronDoor implements Door
{
    public function getDescription()
    {
        echo 'I am an iron door';
    }
}

Cтворення обєктів майстрів

interface DoorFittingExpert
{
public function getDescription();
}

class Welder implements DoorFittingExpert
{
public function getDescription()
{
    echo 'I can only fit iron doors';
}
}

class Carpenter implements DoorFittingExpert
{
public function getDescription()
{
    echo 'I can only fit wooden doors';
}
}

Ствоерння абстрактних фабрик

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();
    }
}

Коли використовувати?

Коли у нас є взаємозв'язоки з не самою простою логікою створення (creation logic).

Паттерн: "Будівельник"

Цей шаблон дозволяє створювати різні обєкти, уникаючи забруднення конструтора (constructor pollution). Це корисно коли у об'єкта може бути декілька властивостей, або стоврення об'єкту складається із великої кількості етапів.

Шаблон "Будівельник" призначений для пошуку рішення проблем антипатерна Telescoping constructor

Прикалад такого антипаттерна:

public function __construct($size, $cheese = true, $pepperoni = true, $tomato = false, $lettuce = true) {}

Велика кількість вхідних параметрів

Розумна алтернатива це шаблон "Будівельник"

Класс бєкта який потрібно створити :

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();

Паттерн: "Прототип"

Об'єкт створюється через клонування існуючого об'єкту

Wiki: Шаблон «Прототип» використовується, коли типи створюваних об'єктів визначаються екземплярами-прототипом, створюючи нові об'єкти.

Тобто шаблон дозволяє дублювати існуючий об'єкт і модифікувати копію у відповідності з потребами. Без необхідності з створенням об'єкта з нуля і його настройкою.

Приклад

class Sheep
{
    protected $name;
    protected $category;

    public function __construct(string $name, string $category = 'Mountain Sheep')
    {
        $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('Jolly');
echo $original->getName(); // Jolly
echo $original->getCategory(); // Mountain Sheep

// Copy and modify
$cloned = clone $original;
$cloned->setName('Dolly');
echo $cloned->getName(); // Dolly
echo $cloned->getCategory(); // Mountain Sheep

Коли використовувати?

Коли необхідний об'єкт аналогічний вже існуючого або коли створення з нуля дорожче клонування.

2018-11-13 03:04:34