Шаблони проектування (Паттерни). Приклади на 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
Коли використовувати?
Коли необхідний об'єкт аналогічний вже існуючого або коли створення з нуля дорожче клонування.