Идеята е инстанцирането на даден клас да не става директно, а да може да се добави някаква допълнителна логика като например, кога кой клас да инстанцираме, дали да го инстанцираме изобщо и т.н…
Тоест – добавяне на логика не само в обектите, а и кога и как изобщо те да бъдат създавани (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:
- Simple Factory Pattern – това не го смятат даже да Д.П., защото е твърде просто – подаваш име на клас на някоя функция, която играе ролята на „фабрика“, евентуално има някаква логика около самото инстанциране с new, и връщаш обекта.
- Factory Method Pattern – тук самата „фабрика“ не е просто една функция както при Simple Factory Pattern, a структура от „фабрика“ и „обекти за произвеждане“, с общ абсклас/интерфейс, организирани за по-лесна взаимозаменяемост (виж примерът по-горе).
- Abstract Factory Pattern – идеята е да се зададе обща структура, която отделните наследяващи фабрики да следват. Демек, имаме повече от една фабрика, но с обща структура.
Eдно голямо предимство и на двата вида Factory Patterns – method и abstract е, че можеш да инджектваш в друг клас, условно наричан „фабрика“ името на класа, който искаш да „изфабрикуваш“, и евентуално аргументи, вместо да инджектваш готов обект.
Демек, казваш на „фабриката“, „на ти името на класа и евентуално аргументи, направи си обекта сам“.
Затова и двете – Factory Method и Factory Abstract спадат към т.н. „creational design patterns“, защото и двете предлагат усложнен, но мощен и гъвкав начин за създаване на обекти.
Друга съществена част от този design pattern е, че всички инстанцирани от фабриката обекти, трябва да са с обща схема и да наследяват един абсклас/интерфейс.
Така че виждаме, че условно можем да разделим Factory Method (но то важи и за Abstract Factory) на две части – едната е самата „фабрика“, другата част е това, което ще произвеждаме, където също трябва да имаме обща структура на продуктите.