Паттерн Factory Method (фабричный метод)

Назначение паттерна Factory Method

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

  • Система должна оставаться расширяемой путем добавления объектов новых типов. Непосредственное использование выражения new является нежелательным, так как в этом случае код создания объектов с указанием конкретных типов может получиться разбросанным по всему приложению. Тогда такие операции как добавление в систему объектов новых типов или замена объектов одного типа на другой будут затруднительными (подробнее в разделе Порождающие паттерны). Паттерн Factory Method позволяет системе оставаться независимой как от самого процесса порождения объектов, так и от их типов.
  • Заранее известно, когда нужно создавать объект, но неизвестен его тип.

Описание паттерна Factory Method

Для того, чтобы система оставалась независимой от различных типов объектов, паттерн Factory Method использует механизм полиморфизма - классы всех конечных типов наследуют от одного абстрактного базового класса, предназначенного для полиморфного использования. В этом базовом классе определяется единый интерфейс, через который пользователь будет оперировать объектами конечных типов.

Для обеспечения относительно простого добавления в систему новых типов паттерн Factory Method локализует создание объектов конкретных типов в специальном классе-фабрике. Методы этого класса, посредством которых создаются объекты конкретных классов, называются фабричными. Существуют две разновидности паттерна Factory Method:

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

UML-диаграмма классов паттерна Factory Method. Обобщенный конструктор

Классический вариант фабричного метода, когда интерфейс фабричных методов объявляется в независимом классе-фабрике, а их реализация определяется конкретными подклассами этого класса.

UML-диаграмма классов паттерна Factory Method. Классическая реализация

Реализация паттерна Factory Method

Рассмотрим оба варианта реализации паттерна Factory Method на примере процесса порождения военных персонажей для нашей стратегической игры. Ее подробное описание можно найти в разделе Порождающие паттерны. Для упрощения демонстрационного кода будем создавать военные персонажи для некой абстрактной армии без учета особенностей воюющих сторон.

Реализация паттерна Factory Method на основе обобщенного конструктора

// #include <iostream>
#include <vector>

enum Warrior_ID { Infantryman_ID=0, Archer_ID, Horseman_ID };

// Иерархия классов игровых персонажей
class Warrior
{
  public:
    virtual void info() = 0;     
    virtual ~Warrior() {}
    // Параметризированный статический фабричный метод
    static Warrior* createWarrior( Warrior_ID id );
};

class Infantryman: public Warrior
{
  public:
    void info() { 
      cout << "Infantryman" << endl; 
    }
};

class Archer: public Warrior
{
  public:
    void info() { 
      cout << "Archer" << endl; 
    }
};

class Horseman: public Warrior
{
  public:    
    void info() { 
      cout << "Horseman" << endl; 
    } 
};


// Реализация параметризированного фабричного метода
Warrior* Warrior::createWarrior( Warrior_ID id )
{
    Warrior * p;
    switch (id)
    {
        case Infantryman_ID:
            p = new Infantryman();           
            break;      
        case Archer_ID:
            p = new Archer();           
            break;
        case Horseman_ID:
            p = new Horseman();           
            break;              
        default:
            assert( false);
    }
    return p;
};


// Создание объектов при помощи параметризированного фабричного метода
int main()
{    
    vector<Warrior*> v;
    v.push_back( Warrior::createWarrior( Infantryman_ID));
    v.push_back( Warrior::createWarrior( Archer_ID));
    v.push_back( Warrior::createWarrior( Horseman_ID));

    for(int i=0; i<v.size(); i++)
        v[i]->info();
    // ...
}

Представленный вариант паттерна Factory Method пользуется популярностью благодаря своей простоте. В нем статический фабричный метод createWarrior() определен непосредственно в полиморфном базовом классе Warrior. Этот фабричный метод является параметризированным, то есть для создания объекта некоторого типа в createWarrior() передается соответствующий идентификатор типа.

С точки зрения "чистоты" объектно-ориентированного кода у этого варианта есть следующие недостатки:

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

Указанные недостатки отсутствуют в классической реализации паттерна Factory Method.

Классическая реализация паттерна Factory Method

// 
#include <iostream>
#include <vector>

// Иерархия классов игровых персонажей
class Warrior
{
  public:
    virtual void info() = 0;     
    virtual ~Warrior() {}
};

class Infantryman: public Warrior
{
  public:
    void info() { 
      cout << "Infantryman" << endl; 
    };
};

class Archer: public Warrior
{
  public:
    void info() { 
      cout << "Archer" << endl; 
    };     
};

class Horseman: public Warrior
{
  public:    
    void info() { 
      cout << "Horseman" << endl; 
    };
};


// Фабрики объектов
class Factory
{
  public:    
    virtual Warrior* createWarrior() = 0;
    virtual ~Factory() {}
};

class InfantryFactory: public Factory
{
  public:    
    Warrior* createWarrior() { 
      return new Infantryman; 
    }
};

class ArchersFactory: public Factory
{
  public:    
    Warrior* createWarrior() { 
      return new Archer; 
    }
};

class CavalryFactory: public Factory
{
  public:    
    Warrior* createWarrior() { 
      return new Horseman; 
    }
};


// Создание объектов при помощи фабрик объектов
int main()
{    
    InfantryFactory* infantry_factory = new InfantryFactory;
    ArchersFactory*  archers_factory  = new ArchersFactory ;
    CavalryFactory*  cavalry_factory  = new CavalryFactory ;

    vector<Warrior*> v;
    v.push_back( infantry_factory->createWarrior());
    v.push_back( archers_factory->createWarrior());
    v.push_back( cavalry_factory->createWarrior());

    for(int i=0; i<v.size(); i++)
        v[i]->info();
    // ...
}

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

Представленный вариант паттерна Factory Method является наиболее распространенным, но не единственным. Возможны следующие вариации:

  • Класс Factoryимеет реализацию фабричного метода createWarrior() по умолчанию.
  • Фабричный метод createWarrior()класса Factoryпараметризирован типом создаваемого объекта (как и у представленного ранее, простого варианта Factory Method) и имеет реализацию по умолчанию. В этом случае, производные от Factoryклассы необходимы лишь для того, чтобы определить нестандартное поведение createWarrior().

Результаты применения паттерна Factory Method

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

  • Создает объекты разных типов, позволяя системе оставаться независимой как от самого процесса создания, так и от типов создаваемых объектов.

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

  • В случае классического варианта паттерна даже для порождения единственного объекта необходимо создавать соответствующую фабрику

results matching ""

    No results matching ""