Factory design pattern

Идеята е инстанцирането на даден клас да не става директно, а да може да се добави някаква допълнителна логика като например, кога кой клас да инстанцираме, дали да го инстанцираме изобщо и т.н…
Тоест – добавяне на логика не само в обектите, а и кога и как изобщо те да бъдат създавани (The business logic of the Creation).

Обикновено, когато ни трябва да предадем някаква капсулирана функционалност – инджектваме готов обект. Да, но може да има случаи, в които не искаме да предаваме готов обект, а името на класа, и да си го инстанцираме по наш, специфичен за нуждите начин.

И ако това въпросно, нужно нам инстанциране, е по-сложно от просто едно new(‘SomeClass’), и искаме също да го организираме по-елегантно откъм OOP, тогава можем да използваме Factory Method или Abstract Factory. Например когато искаме по-лесно да заменяме отделните „фабрики“, правим „сестрински фабрики“ на един абсклас/интерфейс.

abstract class ShapeFabric
{
    // You pass the name of the class, and possibly - arguments...
    public static function getInstance(string $cName, array $args = null): self
    {
        return new $cName($args);
    }
    abstract public function draw(): void;
    abstract public function area(): float;
}

class Circle extends ShapeFabric
{
    protected function __construct(array $args = null)
    {
        echo "Circle created \n";
    }

    public function draw(): void
    {
        echo "Circle drawed \n";
    }

    public function area(): float
    {
        return 123.123;
    }
}

class Rectangle extends ShapeFabric
{
    protected function __construct(array $args = null)
    {
        echo "Rectangle created \n";
    }

    public function draw(): void
    {
        echo "Rectangle drawed \n";
    }

    public function area(): float
    {
        return 234.234;
    }
}

class Triangle extends ShapeFabric
{
    protected function __construct(array $args = null)
    {
        echo "Triangle created \n";
    }

    public function draw(): void
    {
        echo "Triangle drawed \n";
    }

    public function area(): float
    {
        return 345.345;
    }
}

$c = ShapeFabric::getInstance('Circle');
$r = ShapeFabric::getInstance('Rectangle');
echo $r->area();

Виждаме как само с по един ред като например ShapeFabric::getInstance(‘Circle’); или ShapeFabric::getInstance(‘Rectangle’);
можем да създаваме отделни подобни обекти, които си идват с всичко, напълно пълнокръвни обекти.

Защо?
Защото си имаме обектите, които „фабриката да произвежда“, отделно са и подобни (с обща струткура), и само с това кой стринг като име на клас подадем – викаме фабриката като и подаваме съответният стринг за име на клас.

Eстествено, вече се надига въпросът: „ОК, но не може ли просто да си инстанцираме каквото ни трябва с един new Cicrle() или new Rectangle()…“.

Moже естествено, това тук е супер опростен пример.

Но ако искаме още логика в getInstance() например?

Например – някакви проверки дали можем или трябва да създаваме такъв и онакъв обект…?

Или например, нещо подобно на Singleton – да видим дали имаме вече такъв обект създаде и ако да – него да върнем, а да не правим нов?

Или ако искаме да броим от всеки клас по колко обекта вече имаме, и за всеки да имаме лимит?

Или ако искаме да отделим и капсулираме обща за създаването на отделните обекти логика?

Защо трябва да разсъждаваме опростенчески, че създаването на обект е просто да имаме едно new SomeClass() и готово?

Има три типа Factory Design Patterns:

  1. Simple Factory Pattern – това не го смятат даже да Д.П., защото е твърде просто – подаваш име на клас на някоя функция, която играе ролята на „фабрика“, евентуално има някаква логика около самото инстанциране с new, и връщаш обекта.
  2. Factory Method Pattern – тук самата „фабрика“ не е просто една функция както при Simple Factory Pattern, a структура от „фабрика“ и „обекти за произвеждане“, с общ абсклас/интерфейс, организирани за по-лесна взаимозаменяемост (виж примерът по-горе).
  3. Abstract Factory Pattern – идеята е да се зададе обща структура, която отделните наследяващи фабрики да следват. Демек, имаме повече от една фабрика, но с обща структура.

Eдно голямо предимство и на двата вида Factory Patterns – method и abstract е, че можеш да инджектваш в друг клас, условно наричан „фабрика“ името на класа, който искаш да „изфабрикуваш“, и евентуално аргументи, вместо да инджектваш готов обект.
Демек, казваш на „фабриката“, „на ти името на класа и евентуално аргументи, направи си обекта сам“.

Затова и двете – Factory Method и Factory Abstract спадат към т.н. „creational design patterns“, защото и двете предлагат усложнен, но мощен и гъвкав начин за създаване на обекти.

Друга съществена част от този design pattern е, че всички инстанцирани от фабриката обекти, трябва да са с обща схема и да наследяват един абсклас/интерфейс.

Така че виждаме, че условно можем да разделим Factory Method (но то важи и за Abstract Factory) на две части – едната е самата „фабрика“, другата част е това, което ще произвеждаме, където също трябва да имаме обща структура на продуктите.