Abstract Factory створює сім'ї пов'язаних об'єктів без прив'язки до конкретних класів. Клієнт працює з набором інтерфейсів і не знає, яка саме реалізація стоїть за ними.
Яку проблему вирішує
Інколи об'єкти мають бути узгоджені між собою, а не просто взаємозамінні поодинці. Наприклад, пошта і нотифікації одного хмарного провайдера: не можна випадково змішати поштовий клієнт від AWS і нотифікатор від іншого сервісу. Якщо створювати їх розкидано через new, нічого не гарантує, що вся група належить одному провайдеру.
Як вирішує
Один інтерфейс-фабрика оголошує методи створення для кожного члена сім'ї. Конкретна фабрика повертає узгоджений набір реалізацій. Щоб замінити всю сім'ю одразу, достатньо підставити іншу фабрику - решта коду не змінюється.
interface Mailer
{
public function send(string $to, string $body): void;
}
interface Notifier
{
public function notify(string $userId, string $message): void;
}
interface ServicesFactory
{
public function mailer(): Mailer;
public function notifier(): Notifier;
}
Конкретна фабрика збирає сім'ю одного провайдера:
class SesMailer implements Mailer
{
public function send(string $to, string $body): void
{
// SES send
}
}
class SesNotifier implements Notifier
{
public function notify(string $userId, string $message): void
{
// SES notify
}
}
class SesFactory implements ServicesFactory
{
public function mailer(): Mailer
{
return new SesMailer();
}
public function notifier(): Notifier
{
return new SesNotifier();
}
}
Клієнт залежить лише від фабрики й інтерфейсів, не знаючи про SES:
class NotificationService
{
public function __construct(private ServicesFactory $factory) {}
public function run(): void
{
$this->factory->mailer()->send('a@b.com', 'Hello');
$this->factory->notifier()->notify('user1', 'Hi');
}
}
Де застосовувати
- Кілька провайдерів чи конфігурацій, члени яких мусять бути сумісні між собою (хмарні сервіси, теми оформлення, набори драйверів).
- У Laravel роль абстрактної фабрики часто виконує сервіс-контейнер разом із менеджерами драйверів (
Cache, Queue, Mail): метод driver() повертає узгоджену реалізацію за конфігом.
Плюси та мінуси
- + Гарантує сумісність об'єктів усередині однієї сім'ї.
- + Ізолює клієнта від конкретних класів - заміна провайдера в одному місці.
- − Додає шар абстракції та класів; новий член сім'ї потребує правок усіх фабрик.
- − Надлишковий, якщо треба замінювати лише один об'єкт, а не цілу сім'ю - тоді достатньо Factory Method або звичайного зв'язування в контейнері.