Бывает ситуация, когда у нас есть несколько однотипных фабрик и мы хотим инкапсулировать логику выбора, какую из фабрик использовать для той или иной задачи. Тут-то нам на помощь и приходит этот шаблон.
<?php
/**
* Какой-нибудь файл конфигурации
*/
class Config
{
public static $factory = 1;
}
/**
* Какой-то продукт
*/
interface Product
{
/**
* Возвращает название продукта
*
* @return string
*/
public function getName();
}
/**
* Абстрактная фабрика
*/
abstract class AbstractFactory
{
/**
* Возвращает фабрику
*
* @return AbstractFactory - дочерний объект
* @throws Exception
*/
public static function getFactory()
{
switch (Config::$factory) {
case 1:
return new FirstFactory();
case 2:
return new SecondFactory();
}
throw new Exception('Bad config');
}
/**
* Возвращает продукт
*
* @return Product
*/
abstract public function getProduct();
}
/*
* =====================================
* FIRST FAMILY
* =====================================
*/
class FirstFactory extends AbstractFactory
{
/**
* Возвращает продукт
*
* @return Product
*/
public function getProduct()
{
return new FirstProduct();
}
}
/**
* Продукт первой фабрики
*/
class FirstProduct implements Product
{
/**
* Возвращает название продукта
*
* @return string
*/
public function getName()
{
return 'The product from the first factory';
}
}
/*
* =====================================
* SECOND FAMILY
* =====================================
*/
class SecondFactory extends AbstractFactory
{
/**
* Возвращает продукт
*
* @return Product
*/
public function getProduct()
{
return new SecondProduct();
}
}
/**
* Продукт второй фабрики
*/
class SecondProduct implements Product
{
/**
* Возвращает название продукта
*
* @return string
*/
public function getName()
{
return 'The product from second factory';
}
}
/*
* =====================================
* USING OF ABSTRACT FACTORY
* =====================================
*/
$firstProduct = AbstractFactory::getFactory()->getProduct();
Config::$factory = 2;
$secondProduct = AbstractFactory::getFactory()->getProduct();
print_r($firstProduct->getName());
// The first product from the first factory
print_r($secondProduct->getName());
// Second product from second factory
Как видно из примера, нам не приходится заботится о том, какую фабрику взять. Абстрактная фабрика сама проверяет настройки конфигурации и возвращает подходящую фабрику. Разумеется, вовсе не обязательно абстрактная фабрика должна руководствоваться файлу конфигурации. Логика выбора может быть любой.
Используйте если:
Система должна оставаться независимой как от процесса создания новых объектов, так и от типов порождаемых объектов. Непосредственное использование выражения new в коде приложения нежелательно (подробнее об этом в разделе Порождающие паттерны).
Необходимо создавать группы или семейства взаимосвязанных объектов, исключая возможность одновременного использования объектов из разных семейств в одном контексте. Приведем примеры групп взаимосвязанных объектов.
Пусть некоторое приложение с поддержкой графического интерфейса пользователя рассчитано на использование на различных платформах, при этом внешний вид этого интерфейса должен соответствовать принятому стилю для той или иной платформы. Например, если это приложение установлено на Windows-платформу, то его кнопки, меню, полосы прокрутки должны отображаться в стиле, принятом для Windows. Группой взаимосвязанных объектов в этом случае будут элементы графического интерфейса пользователя для конкретной платформы.
Рассмотрим текстовый редактор с многоязычной поддержкой, у которого имеются функциональные модули, отвечающие за расстановку переносов слов и проверку орфографии. Если, скажем, открыт документ на русском языке, то должны быть подключены соответствующие модули, учитывающие специфику русского языка. Ситуация, когда для такого документа одновременно используются модуль расстановки переносов для русского языка и модуль проверки орфографии для немецкого языка, исключается. Здесь группой взаимосвязанных объектов будут соответствующие модули, учитывающие специфику некоторого языка.
В разделе Порождающие паттерны говорилось об игре-стратегии, в которой описывается военное противостояние между армиями Рима и Карфагена. Очевидно, что внешний вид, боевые порядки и характеристики для разных родов войск (пехота, лучники, конница) в каждой армии будут своими. В данном случае семейством взаимосвязанных объектов будут все виды воинов для той или иной противоборствующей стороны, при этом должна исключаться, например, такая ситуация, когда римская конница воюет на стороне Карфагена.
Паттерн Abstract Factory реализуется на основе фабричных методов (см. паттерн Factory Method).
Любое семейство или группа взаимосвязанных объектов характеризуется несколькими общими типами создаваемых продуктов, при этом сами продукты таких типов будут различными для разных семейств. Например, для случая стратегической игры общими типами создаваемых продуктов будут пехота, лучники и конница, при этом каждый из этих родов войск римской армии может существенно отличаться по внешнему виду и боевым характеристикам от соответствуюших родов войск армии Карфагена.
Для того чтобы система оставалась независимой от специфики того или иного семейства продуктов необходимо использовать общие интерфейсы для всех основных типов продуктов. В случае стратегической игры это означает, что необходимо использовать три абстрактных базовых класса для каждого типа воинов: пехоты, лучников и конницы. Производные от них классы будут реализовывать специфику соответствующего типа воинов той или иной армии.
Для решения задачи по созданию семейств взаимосвязанных объектов паттерн Abstract Factory вводит понятие абстрактной фабрики. Абстрактная фабрика представляет собой некоторый полиморфный базовый класс, назначением которого является объявление интерфейсов фабричных методов, служащих для создания продуктов всех основных типов (один фабричный метод на каждый тип продукта). Производные от него классы, реализующие эти интерфейсы, предназначены для создания продуктов всех типов внутри семейства или группы. В случае нашей игры базовый класс абстрактной фабрики должен определять интерфейс фабричных методов для создания пехотинцев, лучников и конницы, а два производных от него класса будут реализовывать этот интерфейс, создавая воинов всех родов войск для той или иной армии.
Приведем реализацию паттерна Abstract Factory для военной стратегии Пунические войны. При этом предполагается, что число и типы создаваемых в начале игры боевых единиц идентичны для обеих армий. Подробное описание этой игры можно найти в разделе [Порождающие паттерны][].
#include <iostream>
#include <vector>
class Infantryman
{
public:
virtual void info() = 0;
virtual ~Infantryman() {}
};
class Archer
{
public:
virtual void info() = 0;
virtual ~Archer() {}
};
class Horseman
{
public:
virtual void info() = 0;
virtual ~Horseman() {}
};
class RomanInfantryman: public Infantryman
{
public:
void info() {
cout << "RomanInfantryman" << endl;
}
};
class RomanArcher: public Archer
{
public:
void info() {
cout << "RomanArcher" << endl;
}
};
class RomanHorseman: public Horseman
{
public:
void info() {
cout << "RomanHorseman" << endl;
}
};
class CarthaginianInfantryman: public Infantryman
{
public:
void info() {
cout << "CarthaginianInfantryman" << endl;
}
};
class CarthaginianArcher: public Archer
{
public:
void info() {
cout << "CarthaginianArcher" << endl;
}
};
class CarthaginianHorseman: public Horseman
{
public:
void info() {
cout << "CarthaginianHorseman" << endl;
}
};
class ArmyFactory
{
public:
virtual Infantryman* createInfantryman() = 0;
virtual Archer* createArcher() = 0;
virtual Horseman* createHorseman() = 0;
virtual ~ArmyFactory() {}
};
class RomanArmyFactory: public ArmyFactory
{
public:
Infantryman* createInfantryman() {
return new RomanInfantryman;
}
Archer* createArcher() {
return new RomanArcher;
}
Horseman* createHorseman() {
return new RomanHorseman;
}
};
class CarthaginianArmyFactory: public ArmyFactory
{
public:
Infantryman* createInfantryman() {
return new CarthaginianInfantryman;
}
Archer* createArcher() {
return new CarthaginianArcher;
}
Horseman* createHorseman() {
return new CarthaginianHorseman;
}
};
class Army
{
public:
~Army() {
int i;
for(i=0; i<vi.size(); ++i) delete vi[i];
for(i=0; i<va.size(); ++i) delete va[i];
for(i=0; i<vh.size(); ++i) delete vh[i];
}
void info() {
int i;
for(i=0; i<vi.size(); ++i) vi[i]->info();
for(i=0; i<va.size(); ++i) va[i]->info();
for(i=0; i<vh.size(); ++i) vh[i]->info();
}
vector<Infantryman*> vi;
vector<Archer*> va;
vector<Horseman*> vh;
};
class Game
{
public:
Army* createArmy( ArmyFactory& factory ) {
Army* p = new Army;
p->vi.push_back( factory.createInfantryman());
p->va.push_back( factory.createArcher());
p->vh.push_back( factory.createHorseman());
return p;
}
};
int main()
{
Game game;
RomanArmyFactory ra_factory;
CarthaginianArmyFactory ca_factory;
Army * ra = game.createArmy( ra_factory);
Army * ca = game.createArmy( ca_factory);
cout << "Roman army:" << endl;
ra->info();
cout << "\nCarthaginian army:" << endl;
ca->info();
}
Вывод программы будет следующим:
Roman army:
RomanInfantryman
RomanArcher
RomanHorseman
Carthaginian army:
CarthaginianInfantryman
CarthaginianArcher
CarthaginianHorseman
Скрывает сам процесс порождения объектов, а также делает систему независимой от типов создаваемых объектов, специфичных для различных семейств или групп (пользователи оперируют этими объектами через соответствующие абстрактные интерфейсы).
Позволяет быстро настраивать систему на нужное семейство создаваемых объектов. В случае многоплатформенного графического приложения для перехода на новую платформу, то есть для замены графических элементов (кнопок, меню, полос прокрутки) одного стиля другим достаточно создать нужный подкласс абстрактной фабрики. При этом условие невозможности одновременного использования элементов разных стилей для некоторой платформы будет выполнено автоматически.
AbstractFactory
и реализовав во всех подклассах. Снять это ограничение можно следующим образом. Все создаваемые объекты должны наследовать от общего абстрактного базового класса, а в единственный фабричный метод в качестве параметра необходимо передавать идентификатор типа объекта, который нужно создать. Однако в этом случае необходимо учитывать следующий момент. Фабричный метод создает объект запрошенного подкласса, но при этом возвращает его с интерфейсом общего абстрактного класса в виде ссылки или указателя, поэтому для такого объекта будет затруднительно выполнить какую-либо операцию, специфичную для подкласса.