Object Pool design pattern

The general idea for the Connection Pool pattern is that if instances of a class can be reused, you avoid creating instances of the class by reusing them.

Основната идея е да имаме дадена data structure, например масив, която да съдържа готови обекти, които можем да използваме когато ни трябват, вместо да създаваме нови, особено когато са „expensive to create“.

Но идеята е както просто да съхраняваме дадената data structure но и да я менажираме като например колко максимално да са обектите, маркиране като „ангажирани“ на тези, които изпозваме, връщане в изходно състояние (reset) на обект, който освобождаваме, унищожаване ненужните и т.н…

Basically, an Object pool is a container which contains some amount of objects. So, when an object is taken from the pool, it is not available in the pool until it is put back.

В този смисъл, Object Pool design pattern е близък по идея с Prototype design pattern, защото елиминира инстанцирането (когато е възможно), което е по принцип „скъпо“.

Както и на Multiton design pattern и до Singleton design pattern, защото предлага кеширане на обекти вместо пре-създаването им всеки път когато ни трябват, но с разликата че Singleton се използва за един обект, от един клас, а не много както тук.

Object pools (otherwise known as resource pools) are used to manage the object caching. A client with access to a Object pool can avoid creating a new Objects by simply asking the pool for one that has already been instantiated instead.

Този design pattern може най-просто да се реализира програмно с помощта на един клас ObjectPoolClass да гарантираме, че ще имаме само един object pool, този singleton клас ще има едни private масив $objects, и ще имаме и например два метода acquire() и release() за извличане на нужният обект и за унищожаване на ненужен.

При тези два метода можем да използваме Factory design pattern, подавайки името на нужният клас, чиито обект ни трябва.

Важно е да се знае, че когато вземем нужният ни обект, друг клиент не трябва да може да го взема, тоест трябва например някак да го маркираме като зает.
A client of the pool will request an object from the pool and perform operations on the returned object. When the client has finished, it returns the object to the pool rather than destroying it; this can be done manually or automatically.

The object pool design pattern creates a set of objects that may be reused. When a new object is needed, it is requested from the pool.
1. If a previously prepared object is available it is returned immediately, avoiding the instantiation cost.
2. If no objects are present in the pool, a new item is created and returned.
3. When the object has been used and is no longer needed, it is returned to the pool, allowing it to be used again in the future without repeating the computationally expensive instantiation process. It is important to note that once an object has been used and returned, existing references will become invalid.

И разбира се, когато го „върнем“ първо трябва да го размаркираме като свободен, и непременно да го върнем в „чисто“ /pristine/, първоначално състояние за следващите „ползватели“.

Care must be taken to ensure the state of the objects returned to the pool is reset back to a sensible state for the next use of the object, otherwise the object may be in an state unexpected by the client, which may cause it to fail.

Също и, по подобни причини, трябва да се внимава със статичните пропъртита, ако такива се използват, защото те могат да се запазват на ниво клас и могат да запазят стойностите си при отделните извиквания на даденият обект от клиент на клиент.

Това важи особено когато даденият обект съдържа поверителна информация.

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

Monostate design pattern

Много подобен на Singleton design pattern, с разликата че при Singleton имаме използване само на един обект от клас, а тук имаме много обекти от даден клас но използват една и съща, споделена информация, което се постига с използването на static class properties.

Демек, данновата им част е една, обща, което значи, че пропъртитата трябва да са статични.

The Monostate pattern is usually referred to as syntactic sugar over the Singleton pattern or as a conceptual Singleton.

This is accomplished mostly by using static data members.
One of the most important feature is that it’s absolutely transparent for the users, that are completely unaware they are working with a Monostate. Users can create as many instances of a Monostate as they want and any instance is good as another to access the data.

За разлика от Singleton, при който имаме само един обект от даден клас, и данните (пропъртитата) му са капсулирани само в него, тук имаме много обекти от даденият клас, но използват едни и същи статични данни (пропъртита) споделено.

Демек, класът е един, неговите обекти са много, просто променяйки едно static property, тази промяна важи за всички обекти на даденият клас.

Builder design pattern

Идеята е ако искаме да създаваме обекти от даден клас, които обаче могат да имат различни вариации. В смислъл, PhoneClass един път да има такива и такива свойства, друг път други.

Ако го направим с един клас PhoneClass, ще трябва на конструктора да му подадем всички възможни свойства. И съответно когато инстанцираме класа, да задаваме само нужните ни за дадената инстанция, а останалите да зададем с default стойност NULL, демек не винаги може да имаме нужда от всички, а може да са доста.
Което автоматично значи, че логиката в констуктурът се усложнява, става неелегантно, конструкторът става огромен и т.н…

Kакво можем да направим – дефинираме си един, така да се каже, общ за всички телефони клас, наречен BarePhoneBuilder, в който ще имаме всичко минимално (като методи, пропъртита и т.н.) за създаването на телефон. Но няма да създаваме обект от този клас, само ще го използваме чрез отделни негови методи и пропъртита (напр. сетъри за да му сетваме различните своиства динамично).

Идеята е да го използваме за да създаваме отделни видове телефони, имащи дадени общи, но и различни, специфични свойства, да викаме само част от методите, тези от които конкретно имаме нужда. Тук идва ролята на т.н. director (или manager) – това е друг клас, който задава какво конкретно трябва да притежава даденият конкретен модел телефон, извиквайки методи и пропъртита на гореспоменатият BarePhoneBuilder.
Тоест, director-ът е на ниво конкретен телефон.

Например, направи ми Galaxy A5!
Значи ще имаме клас GalaxyA5, който ще вика методи от BarePhoneBuilder, които ни трябват в този случай, само който му трябват за да създаде даденият конкретен модел Galaxy A5.

Тези методи ще са setters като им се подава конкретна за даденият модел стойност, те я сетват за дадения обект, може и допълнителна логика да извършват, и накрая връщат $this, за да можем за по-голямо удобство, да „чейнем на влакче“ отделните методи.

И воала – имаме Glaxy A5.

Искаме Nokia FS-12 – пак използваме билдъра BarePhoneBuilder, като „на влакче“ викаме отделните му методи, които ни трябват в този случай, сетваме му отделните им стойности и… имаме Nokia FS-12.

Така вече се вижда цялата структура на Builder design pattern – имаме Билдър клас (BarePhoneBuilder), който ползваме като общ за създаване изобщо на телефони.

Директорът използва от него само каквото му трябва за конкретният случай, задавайки желаните в конкретният случай свойства.

Директорът е на ниво конкретен обект, Билдърът – задава логиката, обща за всички обекти, като Директорът само му подава конкретните стойности.
Демек, „общата“ логика за създаване на конкретният обект е в Билдъра, Директорът само взема един „гол“ обект на Билдъра и и задава останалите свойства. Логиката, която е в Директора е по-скоро за неща като кога кое да бъде зададено, например ако искаме да имаме bluetooth support… и т.н….
Но как конкретно се прави „телефонът“ – в Билдъра.

Също, да не забравяме, че отделните пропърти стойности, които подаваме нa сетърите, могат и да са обекти, така идва и допълнителното определение за Builder design pattern – начин за гъвкаво създаване на обекти, съставени от други обекти (composition).

Kаква е задачата на директорите – да капсулират създаването на отделните конкретни обекти, за да става по-автоматизирано, и също – да скрият създаването от външният потребител.
Искаш Galaxy A5? Инстанцирай класът GalaxyA5 и готово, той си знае работата какво да сетне, какво да има това галакси като параметри и т.н… подробностите не те интересуват.

Естествено, за да можеш да „чейнваш на влакче“, първо трябва да имаш какво да чейнваш. Затова първо създаваш съвсем „гол“ обект, който да е „локомотивът“ и така…

Изводът, който се налага, е че Builder design pattern е удобен когато трябва да създаваме много близки по смисъл обекти, които могат да варират по отделните си свойства, и това би довело до експлозия от параметри в конструктора.

Да, може да има различни по-сложни варианти, например да можем да имаме много близки билдъри, които да наследяват общ абсклас или да имплементират общ интерфейс… но идеята остава същата.

Или обектите, които подаваме като аргументи на отделните сетъри, да екстендават общ абсклас или да имплементират общ интерфейс…

Един добър пример – с роботи:

<?php
/**
 * Задаваме какво задължително всеки робот трябва да има. Може с интерфейс,
 * може с абсклас.
 * Typically there's an abstract Builder class that defines an operation for each
 * component that a director may ask it to create. The operations do nothing by default.
 * A ConcreteBuilder (при нас - BareRobotBuilder) class overrides operations for
 * components it's interested in creating.
 */
interface BareRobotBuilderInterface
{
    public function setRobotHead(string $head) : void;
    public function setRobotTorso(string $torso) : void;
    public function setRobotArms(string $arms) : void;
    public function setRobotLegs(string $legs) : void;
}

/**
 * ...и го дефинираме, всъщност дефинираме методите,
 * които после Директорът ще използва за да създаде конкретен модел робот.
 */
class BareRobotBuilder implements BareRobotBuilderInterface
{
    private string $robotHead, $robotTorso, $robotArms, $robotLegs;

    public function setRobotHead(string $head) : void
    {
        $this->robotHead = $head;
    }

    public function setRobotTorso(string $torso) : void
    {
        $this->robotTorso = $torso;
    }

    public function setRobotArms(string $arms) : void
    {
        $this->robotArms = $arms;
    }

    public function setRobotLegs(string $legs) : void
    {
        $this->robotLegs = $legs;
    }
}

/**
 * Сега нека си зададем какво всеки Директор, който ще произвежда роботи,
 * трябва да може да прави.
 * Понеже идеята е да имаме много модели роботи, нека зададем общ интерфейс.
 */
interface RobotDirectorInterface
{
    public function buildRobotHead() : self;
    public function buildRobotTorso() : self;
    public function buildRobotArms() : self;
    public function buildRobotLegs() : self;
    public function getRobot() : BareRobotBuilderInterface;
}

/**
 * И да създадем един "директор", който ще задава как точно се прави
 * конкеретен модел роботи.
 */
class T1000RobotDirector implements RobotDirectorInterface
{
    public function __construct(
        private BareRobotBuilderInterface $robot
    ) { }

    public function buildRobotHead() : self
    {
        // щом имаме създаден/инджектнат "гол" робот, 
        // започваме да му дефинираме отделните части...
        $this->robot->setRobotHead('Някаква глава от течен метал бла бла…');
        return $this;
    }

    public function buildRobotTorso() : self
    {
        $this->robot->setRobotTorso('Някакво тяло от течен метал бла бла…');
        return $this;
    }

    public function buildRobotArms() : self
    {
        $this->robot->setRobotArms('Някакви ръце от течен метал бла бла…');
        return $this;
    }

    public function buildRobotLegs() : self
    {
        $this->robot->setRobotLegs('Някакви крака от течен метал бла бла…');
        return $this;
    }

    public function getRobot() : BareRobotBuilderInterface
    {
        return $this->robot;
    }
}

$t1000 = (new T1000RobotDirector(new BareRobotBuilder))
  ->buildRobotHead()  // може и да подаваме различните свойства на T-1000 с аргументи
  ->buildRobotTorso() // вместо да са хардкоднати в класа…
  //->buildRobotArms()
  ->buildRobotLegs();
var_dump($t1000->getRobot());

След като вече едно по едно сме задали отделните свойства на нашият робот, единият начин да го получим целият готов е да използваме директно getRobot() на T1000RobotDirector и от там да го върнем.
Нещо такова:

$t1000 = (new T1000RobotDirector(new BareRobotBuilder))
  ->buildRobotHead()  // може и да подаваме различните свойства на T-1000 с аргументи
  ->buildRobotTorso() // вместо да са хардкоднати в класа…
  //->buildRobotArms()
  ->buildRobotLegs();
var_dump($t1000->getRobot());

BareRobotBuilder е реално някакъв общ, абстрактен клас, където ще са само общите за всички роботи неща.
Вместо и така както е – интерфейс + клас, може и да е абстрактен клас например.

Излиза че самото създаване на конкретен робот става в T1000RobotDirector,
и то не точно крайното създаване, а само създаването на отделните части,
без самото сглобяване на целия робот.
Демек, само задава каква да е отделната част – това ще е такова, онова ще е онакова…

Builders construct their products in step-by-step fashion. Therefore the Builder class interface must be general enough to allow the construction of products for all kinds of concrete builders.
Демек Билдър интерфейса/абскласа трябва да е максимално универсален, но Директора вече решава дали да използва всичките му методи.

Друго удобство е, че можем и да не добавяме всички части, ако искаме например робот без ръце и т.н…

Builder design pattern напомня на Abstract factory design pattern но с разликата, че Builder създава обекта си „стъпка по стъпка“ и го връща готов в последната стъпка, а Abstract factory – наведнъж.

It gives you finer control over the construction process. Unlike creational
patterns that construct products in one shot, the Builder pattern constructs the
product step by step under the director’s control. Only when the product is
finished does the director retrieve it from the builder. Hence the Builder
interface reflects the process of constructing the product more than other
creational patterns. This gives you finer control over the construction process
and consequently the internal structure of the resulting product.

Демек, при Builder, създаването на обект е процес, не моментно събитие, което дава повече гъвкавост. Например може преди да добавиш свойство към BareRobotBuilder да искаш да провериш дали не е добавено вече, или дали не трябва нещо друго да е добавено…

Eто най-просто как стъпка по стъпка протича целият процес:
1) The client creates the Director object and configures it with the desired Builder
object.
2) Director notifies the builder whenever a part of the product should be built.
3) Builder handles requests from the director and adds parts to the product.
4) The client retrieves the product from the builder.

Времето тече „от горе надолу“

Prototype design patern

Каква е най-просто идеята?
Идеята е не да СЪЗДАВАМЕ нови инстанции на клас/класове, а да клонираме вече съществуващи ГОТОВИ обекти от съответните класове.

Например ако имаме обекти от даден клас, които са доста близки и се различават само например по едно, две неща, защо да правим нови инстанции на даденият клас? Клонираш обекта и задаваш само различните неща…

Или ако по една или друга причина нямаме достъп до класа, а само до готовият обект.

Как да постигнем това?
Първо, забраняваме __clone() метода на класа/класовете, които ще ползваме като прототипи.

Защо?
За да не позволяваме директно клониране на обектите по принцип, за да си напишем свой custom клонировач, който да клонира.

Следователно, понеже най-вероятно ще имаме повече от един такъв обект, който ще изпозваме за прототип, ще трябва да имеме и интерфейс, с който да задължим тези прототипи да имат такъв метод-клонровач. Демек, да го направим клонируем.

Но защо непременно интерфейс? Moже и абсклас, ако искаме да изнесем и капсулираме клонирането да е общо за всички клонирани, демек да става по един и същ начин.

Кои са „участниците“ в Prototype design pattern?

  1. Всички класове, които ще могат да бъдат клонирани трябва да имплементират общ интерфейс, който ще наречем Prototype.
  2. Те (отделните класове, които ще клонираме) ще наричаме ConcretePrototype, и те по задължение от горният интерфейс, ще имат метод който ще извършва самото клониране, например cloneMe() (демек, implements an operation for cloning itself)
  3. И разбира се Client, който ще изисква и използва тези клонинги, демек by asking a prototype to clone itself.

Демек, Клиентът казва на Конкретният прототип „клонирай се и ми дай клонинга си“, като му вика cloneMe()

Multiton design pattern /Registry of Singletons/

Този Design Pattern е така да се каже разширение на Singleton Design Pattern и също е известна като „Registry of Singletons“.

В смисъл такъв, че Singleton гарантира само един обект от даденият клас, а Multiton – повече от един обект от СЪЩИЯТ клас, но съдържащи се в хеш таблица или масив на друг обект.
…the multiton pattern instead ensures a single instance per key.

Представете си го като асоциативен масив с много съставки от дадена кулинарна рецепта, но от съставка можем да имаме само по една:

static private $ingredients = null;

...

self::$ingredients['tomato'] = Ingredients.getInstance('tomato');
self::$ingredients['pepper'] = Ingredients.getInstance('pepper');
self::$ingredients['cheese'] = Ingredients.getInstance('cheese');

...

и разбира се за всяка съставка, трябва първо да проверяваме дали вече не е добавена, тоест, дали вече имаме стойност в масива $shopskaSalad за даденият ключ.

...

if (self::$ingredients['tomato'] === null)
    self::$ingredients['tomato'] = Ingredients.getInstance('tomato');

if (self::$ingredients['pepper'] === null)
    self::$ingredients['pepper'] = Ingredients.getInstance('pepper');

if (self::$ingredients['cheese'] === null)
    self::$ingredients['cheese'] = Ingredients.getInstance('cheese');

...

As the name suggests, a multiton is a design pattern that helps you create multiple instances of itself. Both singleton and multiton are same, the only difference is that a multiton can store and retrieve multiple instances of itself. Obviously multiton suffers from same problems as singletons.

Here is example of multiton:

class Logger
{
    private static $instances = array();

    public static function getInstance(string $key): self
    {
        if (!array_key_exists($key, self::$instances)) {
            self::$instances[$key] = new self();
        }
        return self::$instances[$key];
    }

    // prevent creating multiple instances due to "private" constructor
    private function __construct(){}

    // prevent the instance from being cloned
    private function __clone(){}

    // prevent from being unserialized
    public function __wakeup(){}
}

$firstInstance = Logger::getInstance('file');
$secondInstance = Logger::getInstance('email');

Литература:

https://en.wikipedia.org/wiki/Multiton_pattern

https://www.codeproject.com/Articles/1178694/Singleton-and-Multiton-Pattern

https://codeinphp.github.io/post/singleton-and-multiton-design-patterns/

Singleton design pattern

Ensure a class only has one instance, and provide a global point of access to it.

Използва се за случаите, в които искаме да гарантираме, че от даден клас ще имаме само един обект. Например при операции, които заемат повече памет, като обекти за работа с бази данни, файлове и т.н…

Също, може да се използва при нужда от т.н. lazy instantiation – отлагане създаването на обект, до момента в който вече наистина ни трябва.

Най-просто – правим инстанцирането „отвън“ (с new …) невъзможно като направим конструктора да е private. Също и клонирането с __clone() или десериализирането с __wakeup()

Инстанцирането ще става от обикновен публичен и статичен метод, например getInstance(), който ще ни е т.н. access point.

Декларираме си статична private променлива на класа, например $inst, в която ще съхраняваме обектът от класа.

И когато извикаме getInstance() за да ни върне обект, първо проверяваме дали вече имаме стойност в $inst, тоест обект, или е NULL и ако е NULL присвояваме и обект от класа със new self(); което пък ще извика private конструктора.

Ако има обект вече в $inst – връщаме го директно, без да правим нов.

Добре е да се знае, че при Singleton Pattern деструкторът на класа също се вика автоматично или при unset() както обикновено, и трябва да е public.
Aкo не е public ще имаме грешка Call to private SomeClass::__destruct() …

Singleton design pattern е доста проста и даден не-singleton клас може лесно да се преобразува в singleton такъв само с добавяне на метод за access point демек getInstance(), едно static private пропърти и да направим конструктора и __clone да са private.

Също, на не-singleton клас можем да направим наследника да е singleton.
Както и наследникът на singleton не е автоматично singleton.

Според мен, въпреки че Singleton DP е масово считан за antipattern и трябвало било да бъде избягван на всяка цена…, аз твърдя точно обратното – Singleton е един чудесен design pattern, но просто проблемът е, че е използван грешно. Toчно простотата му му изиграва лоша шега. Не може да използваш чук за да завиеш гайка, или отвертка – за да забиеш гвоздей, и да са ти виновни съответните инструменти. Виновен си ти като „майстор“, че ги използваш грешно.

Singleton създава един напълно пълнокръвен и обикновен обект, просто следи дали вече обект от този клас не съществува. Какво лошо има в това ако например създаването на този обект е „тежко“ и/или този обект е огромен, а във същото време неговите properties са непроменливи. Например – обект с различни config settings? Защо да не използваме Singleton?

Не е нужно да си „Айнщайн“ за да разбереш, че когато на Singleton обект му промениш на едно място някое/някои пропъртита, ще важи глобално… ми нали е един обект!

Не е нужно да си „Айнщайн“ за да разбереш, че е по-добре на инджектваш обект отвън, а не да инстанцираш вътре във функцията/методът, защото така нещата стават not-testable.

Явно тези грешки се допускат масово и от там разни „айнщайновци“ носят лошото име на Singleton DP, и тези „айнщайновци“ допускат такива грешки, точно защото Singleton DP е прост и лесен.

Литература:

https://www.codeproject.com/Articles/1178694/Singleton-and-Multiton-Pattern

Abstract Factory design pattern

Или с други думи „Фабрика за фабрики“. И тази „фабрика за фабрики“ е под формата на абсклас/интерфейс, който повелява на под-фабриките си какъв тип продукти трябва да произвеждат, а не само един продукт както при Factory Method.

Представяме си един конгломерат (напр. General Motors) от много марки автомобили като Шевролет, Понтиак, Кадилак…

GM налага на отделните си марки, че всички техни коли трябва задължително да имат например 3 неща – двигател, спирачки и климатик.
Но какви конкретно – те си решават.

И ето как се оформят следната основна структура от класове, която задава същността на този design pattern – абсклас/интерфейс GM, който има 3 абсметода createEngine(), createBrakes() и createAirconditioner().
И „под-фабриките“ му – класовете Chevrolet, Pontiac и Cadillac, които трябва да ги имплементират/наследяват.

Какво трябва да има във всяка имплементация на всяка от тези под-фабрики (или concrete factory)? Да създават обекти от други класове – за различните марки двигатели, спирачки и климатици.

Да се върнем на определението: „The Abstract Factory Pattern provides an interface for creating families of related or dependent objects“.
Какви семейства или групи от свързани или зависими обекти? Ако вземем под-фабриката Chevrolet тя какво произвежда? Шевролети ще произвежда разбира се. Какво има във всеки шевролет задължително? Двигател, спирачки и климатик.

Както и Pontiac, и Cadillac… всички отделни марки, които GM произвежда (респективно наследяват/имплементират GM) също трябва да имат поне двигател, спирачки и климатик.

Тези три обекта не са ли „related“ помежду си? Са естествено.

Къде е „interface for creating…“? Kласът GM, който повелява на подфабриките си, че всяка трябва да има винаги поне трите неща.
На шевролета, понтиака или кадилака, какъв ще му е конкретно двигателят, спирачките и климатика – те вече си решават, важното е да не произведат нещо без тези трите неща.

„interface for creating families of related objects“ – това е цялата философия.

Вече отделно за всеки от трите задължителни компонента трябва да си направим отделни йерархии – например клас Engine, който го наследяват класове VolvoPenta, Trabant… клас Brakes, който го наследяват класове…., клас Airconditioner, който го наследяват Carrier, Daikin, Inventor…
Затова се казва също и „…without specifying their concrete classes…“. Демек, не казваш всяка марка какъв конкретен двигател да има, а да има двигател изобщо.

Защо трябва?

За да може в класа GM да зададем, че първият абсметод трябва да връща обект от клас Engine, а не конкретно VolvoPenta (нали казахме, че е важно да имаме двигател, без значение марката).
Съответно вторият, третият и т.н. абсметоди трябва да връщат обекти от клас Brakes и Aircondiotioners…

Разликата с Factory Method e, че там имаш един factory method, който създава един обект. Тук можеш да обединяваш различни factory methods в групи. Точно както заводът за двигатели, спирачки или климатици прави само това, а заводът за шевролети, кадилаци и понтиаци комбинира отделните техни продукти (двигатели, спирачки и климатици).

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

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

Основното предимство и полза от Abstract design pattern е капсулиране на „производството“ на близки по структура обекти.

А от „близки по структура“ идва и другото му предимство – взаимозаменяемостта им.

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) на две части – едната е самата „фабрика“, другата част е това, което ще произвеждаме, където също трябва да имаме обща структура на продуктите.