Паттерн Абстрактная фабрика (Abstract Factory) — уровень объекта

Название и классификация паттерна

Абстрактная фабрика — паттерн, порождающий объекты.

Назначение

Предоставляет интерфейс для создания семейств взаимосвязанных или взаимозависимых объектов, не специфицируя их конкретных классов.

Применимость

Использование паттерна Abstract Factory (абстрактная фабрика) целесообразно, если:

  • • система не должна зависеть от того, как создаются, компонуются и представляются входящие в нее объекты;
  • • входящие в семейство взаимосвязанные объекты должны использоваться вместе, и вам необходимо обеспечить выполнение этого ограничения;
  • • система должна конфигурироваться одним из семейств составляющих ее объектов;
  • • вы хотите предоставить библиотеку объектов, раскрывая только их интерфейсы, но не реализацию.

Приведем примеры групп взаимосвязанных объектов.

Пусть некоторое приложение с поддержкой графического интерфейса пользователя рассчитано на использование на различных платформах, при этом внешний вид этого интерфейса должен соответствовать принятому стилю для той или иной платформы. Например, если это приложение установлено на Windows-платформу, то его кнопки, меню, полосы прокрутки должны отображаться в стиле, принятом для Windows. Группой взаимосвязанных объектов в этом случае будут элементы графического интерфейса пользователя для конкретной платформы.

Другой пример. Рассмотрим текстовый редактор с многоязычной поддержкой, у которого имеются функциональные модули, отвечающие за расстановку переносов слов и проверку орфографии. Если открыт документ на русском языке, то должны быть подключены соответствующие модули, учитывающие специфику русского языка. Ситуация, когда для такого документа одновременно используются модуль расстановки переносов для русского языка и модуль проверки орфографии для немецкого языка, исключается. Здесь группой взаимосвязанных объектов будут соответствующие модули, учитывающие специфику некоторого языка.

И последний пример. Выше говорилось об игре-стратегии «Пунические войны». Очевидно, что внешний вид, боевые порядки и характеристики для разных родов войск (пехота, лучники, конница) в каждой армии будут своими. В данном случае семейством взаимосвязанных объектов будут все виды воинов для той или иной противоборствующей стороны, при этом должна исключаться, например, такая ситуация, когда римская конница воюет на стороне Карфагена.

Структура

Структура паттерна Абстрактная фабрика показана на рис. 35.

UML-диаграмма классов паттерна Abstract Factory

Рис. 35. UML-диаграмма классов паттерна Abstract Factory

Участники

AbstractFactory — абстрактная фабрика: объявляет интерфейс для операций, создающих абстрактные объекты-продукты.

ConcreteFactory (ConcreteFactoryl, ConcreteFactory2) — конкретная фабрика: реализует операции, создающие конкретные объекты-продукты (для игры «Пунические войны» создаются армии Рима и Карфагена).

AbstractProduct (Abstract Product A, Abstract Product В) — абстрактный продукт: объявляет интерфейс для типа объекта-продукта.

ConcreteProduct (ProductA, Product В) — конкретный продукт: определяет объект-продукт, создаваемый соответствующей конкретной фабрикой (например, лучник, всадник), — реализует интерфейс Abstract Product.

Client — клиент: пользуется исключительно интерфейсами, которые объявлены в классах AbstractFactory и AbstractProduct.

Отношения

Обычно во время выполнения создается единственный экземпляр класса ConcreteFactory. Эта конкретная фабрика создает объекты-продукты, имеющие вполне определенную реализацию. Для создания других видов объектов клиент должен воспользоваться другой конкретной фабрикой.

Abstract Factory передоверяет создание объектов-продуктов своему подклассу ConcreteFactory.

Результаты

Паттерн Абстрактная фабрика обладает следующими плюсами и минусами:

  • изолирует конкретные классы. Помогает контролировать классы объектов, создаваемых приложением. Поскольку фабрика инкапсулирует ответственность за создание классов и сам процесс их создания, то она изолирует клиента от деталей реализации классов. Клиенты манипулируют экземплярами через их абстрактные интерфейсы. Имена изготавливаемых классов известны только конкретной фабрике, в коде клиента они не упоминаются;
  • упрощает замену семейств продуктов. Класс конкретной фабрики появляется в приложении только один раз: при инстанцировании. Это облегчает замену используемой приложением конкретной фабрики. Приложение может изменить конфигурацию продуктов, просто подставив новую конкретную фабрику. Поскольку абстрактная фабрика создает все семейство продуктов, то и заменяется сразу все семейство. В нашем примере пользовательского интерфейса перейти от виджетов Motif к виджетам Presentation Manager можно, просто переключившись на продукты соответствующей фабрики и заново создав интерфейс;
  • гарантирует сочетаемость продуктов. Если продукты некоторого семейства спроектированы для совместного использования, то важно, чтобы приложение в каждый момент времени работало только с продуктами единственного семейства. Класс Abstract Factory позволяет легко соблюсти это ограничение;
  • поддержать новый вид продуктов трудно. Расширение абстрактной фабрики для изготовления новых видов продуктов — непростая задача. Интерфейс Abstract Factory фиксирует набор продуктов, которые можно создать. Для поддержки новых продуктов необходимо расширить интерфейс фабрики, т. е. изменить класс AbstractFactory и все его подклассы.

Пример кода для паттерна Abstract Factory

Приведем реализацию паттерна Abstract Factory для военной стратегии «Пунические войны». При этом предполагается, что число и типы создаваемых в игре боевых единиц идентичны для обеих армий. Каждая армия имеет в своем составе пехотинцев (Infantryman), лучников (Archer) и кавалерию (Horseman).

Структура паттерна для данного случая представлена на рис. 36.

Client

ArmyFactory

Infantryman

г

Roman Infantryman

Carthaginianlnfantryman

+InfantrymanO

+Archer()

+Horseman О

- Carthaginian ArmyFactory

Roman ArmyFactory

+Carthaginianlnfantryman()

+CarthaginianArcher()

+CarthaginianHorseman()

+RomanInfantryman()

+RomanArcher()

+RomanHorsemanQ

I A

— -> RomanArcher

Archer

Carthaginian Archer

Horseman

L

L

О

Roman Horseman

CarthaginianHorscnian

- “I __I

Рис. 36. UML-диаграмма классов для военной стратегии «Пунические войны»

#include

#include

// Абстрактные базовые классы всех возможных видов воинов 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 Romanlnfantryman: public Infantryman {

public: void info() {

cout << "Romanlnfantryman" << endl;

}

class RomanArcher: public Archer

{

public: void info() {

cout << "RomanArcher" << endl;

}

};

class RomanHorseman: public Horseman

{

public: void info() {

cout << "RomanHorseman" << endl;

}

};

// Классы всех видов воинов армии Карфагена class Carthaginianlnfantryman: public Infantryman

{

public: void info() {

cout << "Carthaginianlnfantryman" << 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* createlnfantryman() = 0; virtual Archer* createArcher() = 0; virtual Horseman* createHorseman() = 0; virtual ~ArmyFactory() {}

};

// Фабрика для создания воинов римской армии class RomanArmyFactory: public ArmyFactory {

public:

Infantryman* createlnfantryman() { return new Romanlnfantryman;

}

Archer* createArcher() { return new RomanArcher;

}

Horseman* createHorseman() { return new RomanHorseman;

}

};

// Фабрика для создания воинов армии Карфагена class CarthaginianArmyFactory: public ArmyFactory {

public:

Infantryman* createlnfantryman() { return new Carthaginianlnfantryman;

}

Archer* createArcherQ { return new CarthaginianArcher;

}

Horseman* createHorsemanQ { return new Carthaginian Horseman;

}

};

// Класс, содержащий всех воинов той или иной армии class Army {

public:

~Army() { int i;

for(i=0; i

}

void info() { int i;

for(i=0; iinfo(); for(i=0; iinfo(); for(i=0; iinfo();

}

vector vi; vector< Archer* > va; vector 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 * га = game.createArmy( ra_factory);

Army * са = game.createArmy( ca_factory); co?t << "Roman army:" << endl; ra->info();

cout << " Carthaginian army:" << endl; ca->info();

//-

Вывод программы будет следующим:

Roman army:

Roman Infantryman

RomanArcher

RomanHorseman

Carthaginian army:

Carthaginianlnfantryman

CarthaginianArcher

Carthaginian Horseman

Достоинства паттерна Abstract Factory

Скрывает сам процесс порождения объектов, а также делает систему независимой от типов создаваемых объектов, специфичных для различных семейств или групп (пользователи оперируют этими объектами через соответствующие абстрактные интерфейсы).

Позволяет быстро настраивать систему на нужное семейство создаваемых объектов. В случае многоплатформенного графического приложения для перехода на новую платформу, т. е. для замены графических элементов (кнопок, меню, полос прокрутки) одного стиля другим, достаточно создать нужный подкласс абстрактной фабрики. При этом условие невозможности одновременного использования элементов разных стилей для некоторой платформы будет выполнено автоматически.

Недостатки паттерна Abstract Factory

Трудно добавлять новые типы создаваемых продуктов или заменять существующие, так как интерфейс базового класса абстрактной фабрики фиксирован. Например, если для нашей стратегической игры нужно будет ввести новый вид военной единицы — осадные орудия, то надо будет добавить новый фабричный метод, объявив его интерфейс в полиморфном базовом классе AbstractFactory и реализовав во всех подклассах. Снять это ограничение можно следующим образом. Все создаваемые объекты должны наследовать от общего абстрактного базового класса, а в единственный фабричный метод в качестве параметра необходимо передавать идентификатор типа объекта, который нужно создать. Однако в этом случае необходимо учитывать следующий момент. Фабричный метод создает объект запрошенного подкласса, но при этом возвращает его с интерфейсом общего абстрактного класса в виде ссылки или указателя, поэтому для такого объекта будет затруднительно выполнить какую-либо операцию, специфичную для подкласса.

Родственные паттерны

Классы Abstract Factory часто реализуются фабричными методами (паттерн Фабричный метод), но могут быть реализованы и с помощью паттерна Прототип. Конкретная фабрика может быть описана паттерном Одиночка.

 
< Пред   СОДЕРЖАНИЕ     След >