Описывается порождающий шаблон проектирования Abstract factory (Абстрактная фабрика), его применение, приведён пример реализации шаблона на PHP.
Содержание
- Применение
- Структура шаблона
- Реализация
- Итоги
Применение
Шаблон проектирования Abstract factory применяют в случае, когда клиентскому коду требуется получить семейство связанных объектов, клиент при этом не обязан знать конкретные типы создаваемых объектов. Под связанными здесь подразумевается, что создаваемые объекты принадлежат какой-то определенной группе товаров и должны использоваться вместе. Например, это могут быть сборочные узлы для конкретного изделия.
Структура шаблона
Итак, шаблон создает семейство продуктов, под которыми подразумеваются программные объекты, используемые совместно. Для этого создаются две различные иерархии классов – собственно продуктов и фабрик по их созданию. Первая определяет поведение и свойства нужных нам продуктов, для каждого продукта свои. Вторая иерархия служит для создания набора конкретных продуктов. Родителей этих иерархий можно реализовать как через программный интерфейс, так и через абстрактный класс, которые потом реализуются в классах-наследниках.
Нагляднее будет пояснить сказанное на конкретном примере. В нём будет создавать сборочные узлы UnitOne и UnitTwo для изделий различных брендов А и В. Таким образом, в нашем примере Абстрактной фабрики участвуют.
- UnitOne — интерфейс, определяющий набор методов одного из узлов продукта.
- UnitTwo — интерфейс, определяющий набор методов другого узла продукта.
- BrandAUnitOne — класс сборочного узла One бренда A, реализует интерфейс UnitOne.
- BrandAUnitTwo — класс сборочного узла Two бренда A, реализует интерфейс UnitTwo.
- BrandBUnitOne — класс сборочного узла One бренда B, реализует интерфейс UnitOne.
- BrandBUnitTwo — класс сборочного узла Two бренда B, реализует интерфейс UnitTwo.
- Factory — interface, определяющий поведение фабрик сборочных узлов.
- BrandAFactory — класс, создающий объекты узлов конкретного бренда А. Реализует интерфейс Factory.
- BrandBFactory — класс, создающий объекты узлов конкретного бренда В. Реализует интерфейс Factory.
Реализация
Определим интерфейсы требуемых узлов.
<?php
declare(strict_types=1);
interface UnitOne
{
public function getUnitOneName(): string;
}
interface UnitTwo
{
public function getUnitTwoName(): string;
}
Далее создаём классы семейства конкретных продуктов.
// --- Конкретные узлы бренда А ---
class BrandAUnitOne implements UnitOne
{
public function getUnitOneName(): string
{
return 'UnitOne of brand A';
}
}
class BrandAUnitTwo implements UnitTwo
{
public function getUnitTwoName(): string
{
return 'UnitTwo of brand A';
}
}
// --- Конкретные узлы бренда B ---
class BrandBUnitOne implements UnitOne
{
public function getUnitOneName(): string
{
return 'UnitOne of brand B';
}
}
class BrandBUnitTwo implements UnitTwo
{
public function getUnitTwoName(): string
{
return 'UnitTwo of brand B';
}
}
Необходимые объекты определены, теперь определяем иерархию фабрик, создающих эти объекты. Начнем с определения общего интерфейса.
// --- ФАБРИКИ: Интерфейс абстрактной фабрики ---
interface Factory
{
public function createUnitOne(): UnitOne;
public function createUnitTwo(): UnitTwo;
}
На основе созданного интерфейса фабрики определяем классы фабрик, создающих узлы брендов.
class BrandAFactory implements Factory
{
public function createUnitOne(): UnitOne
{
return new BrandAUnitOne();
}
public function createUnitTwo(): UnitTwo
{
return new BrandAUnitTwo();
}
}
class BrandBFactory implements Factory
{
public function createUnitOne(): UnitOne
{
return new BrandBUnitOne();
}
public function createUnitTwo(): UnitTwo
{
return new BrandBUnitTwo();
}
}
Теперь продемонстрируем, как клиент использует шаблон. Т.к. клиент взаимодействует с фабриками и продуктами через их интерфейсы, это позволяет передавать в один и тот же код любую фабрику без его изменения.
function reportProductFamily(Factory $factory): void
{
// Фабрика сама знает, продукты какого бренда нужно создать
$unitOne = $factory->createUnitOne();
$unitTwo = $factory->createUnitTwo();
// Выводим отчет
echo "Созданные продукты:\n";
echo "- " . $unitOne->getUnitOneName() . "\n";
echo "- " . $unitTwo->getUnitTwoName() . "\n";
echo "-----------------------------\n";
}
echo "=== Отчет по узлам бренда А ===\n";
$factoryA = new BrandAFactory();
reportProductFamily($factoryA);
echo "=== Отчет по узлам бренда B ===\n";
$factoryB = new BrandBFactory();
renderProductFamily($factoryB);
Итоги
Подведем итоги, для чего здесь было городить кучу кода.
Цель шаблона абстрактной фабрики — переложить работу по созданию семейства связанных объектов на отдельный код приложения. Этот код точно знает, какие объекты создавать, поэтому при добавлении нового бренда в приложение, мы создаем новый код, не корректируя существующий.
При необходимости расширить функциональность продуктов мы можем создать еще один интерфейс и реализовать его в наследниках продуктов. Все это будет происходить во вновь создаваемом коде, код же приложения при этом практически не изменяется.
Достоинства.
- Упрощает поддержку и модификацию проекта выделяя код по созданию семейства продуктов, а не размазывая его по приложению.
- Клиентский код не привязывается к конкретным классам продуктов.
- Гарантированно создает строго определенные связанные продукты.
- Упрощает добавление новых продуктов без внесения изменений в существующий код.
Недостатки.
- Усложняется код приложения из-за создания дополнительных классов и интерфейсов.
Сложно вводить новый тип продуктов, если его интерфейс не вписывается в существующий набор функций продукта. - Базовый интерфейс фабрики жестко фиксирует набор создаваемых продуктов, это ограничивает гибкость архитектуры при внесении изменений в нее.
Успехов в разработке!
Вернуться к списку шаблонов проектирования.