Абстрактная фабрика в PHP
Короткий скринкаст: семейство продуктов и фабрик строится на общих интерфейсах — это паттерн «Абстрактная фабрика» (Abstract Factory).
Описание
В этом скринкасте мы строим паттерн на интерфейсах. ProductInterface описывает продукт, FactoryInterface — фабрику. Конкретные фабрики LaptopFactory и SmartphoneFactory создают соответствующие продукты, а клиентский код (demo.php) работает только с абстракциями — можно подменить одну «семью» объектов другой, не меняя вызывающий код.
UML-диаграмма
Код
ProductInterface.php
interface ProductInterface
{
public function getInfo(): string;
}
Laptop.php
class Laptop implements ProductInterface
{
public function __construct(
private string $name
) {}
public function getInfo(): string
{
return 'Laptop: ' . $this->name;
}
}
Smartphone.php
class Smartphone implements ProductInterface
{
public function __construct(
private string $model
) {}
public function getInfo(): string
{
return 'Smartphone: ' . $this->model;
}
}
FactoryInterface.php
interface FactoryInterface
{
public function createProduct(): ProductInterface;
}
LaptopFactory.php
class LaptopFactory implements FactoryInterface
{
public function createProduct(): ProductInterface
{
return new Laptop('MacBook Pro');
}
}
demo.php
require_once 'ProductInterface.php';
require_once 'Laptop.php';
require_once 'FactoryInterface.php';
require_once 'LaptopFactory.php';
$factory = new LaptopFactory();
$product = $factory->createProduct();
echo $product->getInfo();
Заметка: почему интерфейсы
Клиент в demo.php не знает про Laptop и LaptopFactory — только про ProductInterface и FactoryInterface. Это и есть суть паттерна: код зависит от абстракций, а какую «семью» объектов собрать, решает конкретная фабрика. Добавить SmartphoneFactory — и клиент соберёт смартфон без изменения вызывающего кода.
Заметка: почему интерфейс, а не абстрактный класс
В PHP на класс приходится только одно наследование, а интерфейсов можно реализовать сколько угодно. В абстрактной фабрике члены одной «семьи» обычно не имеют общей реализации — их объединяет только контракт, поэтому подходит именно интерфейс:
abstract class AbstractProduct
{
abstract public function getInfo(): string;
}
Такая абстракция сразу «съедает» единственное наследование: Laptop не сможет одновременно унаследовать, скажем, базовый класс модели вашего фреймворка. Плюс абстрактный класс смешивает контракт и реализацию — стоит положить туда общий метод, и все продукты семьи «втянут» его даже без необходимости, а подменить семью станет сложнее. Интерфейс задаёт только тип и поведение, оставляя реализацию каждому продукту.
Пример результата
После php demo.php в терминале появится:
Laptop: MacBook Pro
Преимущества
- Позволяет работать с целым семейством согласованных продуктов.
- Клиент не зависит от конкретных классов — только от абстрактных интерфейсов.
- Замена семейства (например, вендора) не затрагивает клиентский код.
- Гарантирует совместимость продуктов, созданных вместе.
Когда использовать
- Когда система должна быть независима от того, как создаются её продукты.
- Когда продуктов несколько и они образуют согласованные семейства.
- Когда нужно переключать целые наборы продуктов во время выполнения.
- Когда важно, чтобы продукты одного семейства использовались вместе.
Использование в Laravel
В Laravel абстрактная фабрика — это менеджеры драйверов: Illuminate\Cache\CacheManager, Illuminate\Filesystem\FilesystemManager, Illuminate\Queue\QueueManager. Каждый создаёт не один объект, а согласованное семейство реализаций (стор кэша, диск или очередь) по имени драйвера из конфигурации. Через Cache::store('redis') или Storage::disk('s3') клиент получает нужное семейство, не зная конкретных классов.