Порождающий паттерн. Фабричный метод

Определяет интерфейс для создания объектов, при этом требуемый класс создается с помощью подклассов. В подклассе определяется метод(как раз фабричный метод), который возвращает экземпляр конкретного подкласса.
Фабричные методы избавляют проектировщика от необходимости встраивать в код зависящие от приложения классы. Код имеет дело только с интерфейсом надкласса, поэтому он может работать с любыми определенными пользователями конкретных продуктов.
Использовать этот паттерн можно в следующих случаях:
  • классу заранее не известно, объекты каких классов ему нужно создавать
  • класс спроектирован так, чтобы объекты, которые он создает, специфировались подклассами
  • класс делегирует свои обязанности одному из нескольких вспомогательных подклассов, и вы планируете локализовать знание о том, какой класс принимает эти обязанности на себя
  • когда вы хотите дать возможность пользователям расширять части вашей библиотеки
  • когда вы хотите экономить системные ресурсы при создании новых объектов(часть общах данных можно хранить в кэше, и копировать их от туда в новые объекты) 
Состоит из:
  • Product - продукт. Определяет интерфейс объектов, создаваемых абстрактным методом
  • ConcreteProduct - конкретный продукт. Реализует интерфейс Product. Содержит код, каждого конкретного продукта. Реализация у каждого своя, а вот интерфейс общий. 
  • Creator - создатель. Объявляет фабричный метод, который возвращает объект типа Product. Может вызывать фабричный метод для создания объекта типа Product. Часто фабричный метод объявляют абстрактным, чтобы заставить все подклассы реализовать фабричный метод по своему, но иногда он может возвращать объект по умолчанию. 
  • ConcreteCreator - конкретный создатель. Переопределяет фабричный метод таким образом, чтобы он создавал и возвращал объект класса ConcreteProduct, но он не обязан все время создавать объекты, он может возвращать существующие объекты их кэша к примеру.
Для того, чтобы система оставалась независимой от различных типов объектов, этот паттерн использует механизм наследования - классы всех конечных типов наследуют от одного абстрактного базового класса, предназначенного для полиморфного использования. В этом базовом классе определяется единый интерфейс, через который пользователь будет оперировать объектами конечных типов.

Преимущества
  • Избавляет класс от привязки к конкретным классам наследникам
  • Выделяет код создания конкретных объектов в одно место, тем самым упрощая сопровождение кода
  • Упрощает добавление новых типов объектов в программу
Из недостатков, выделяют один - даже для создания одного единственного объекта(ConcreteProduct) надо создать свой подкласс(свою фабрику) создателя(Creator).

Практически все вышеописанное касается класической схемы фабричного метода.
Но ничего не мешает, нашему фабричному методу немного мутировать, и немного отойти от класической схемы. Главное помнить, паттерны это общие описания решений у которых уже есть устоявшиеся имена, используя в разговорах которые, вы можете понять своего товарища программиста а он вас(понять как устроен тот или иной участок программы).
На основе этого паттерна реализуется паттерн Абстрактная фабрика(Abstract Factory).

Смотрите ниже примеры реализаций этого паттерна, в класической схеме и не только.
Одна из реализаций без этого паттерна:
struct baseTank
{
  virtual void draw() = 0;
  virtual ~baseTank() = default;
};

struct t34USA : baseTank
{
  void draw() override { std::cout << "I am heavy tank T34(USA)" << std::endl; }
};

struct t34USSR : baseTank
{
  void draw() override { std::cout << "I am T-34(USSR)" << std::endl; }
};

void example()
{
  std::srand(time(0));

  using uptr = std::unique_ptr<baseTank>;

  std::vector<uptr> tanks;

  int size = (std::rand() % 10) + 1;
  for(int i = 0; i < size; ++i)
  {
    int rval = std::rand() % 2;
    switch(rval)
    {
      case 0:
      {
        tanks.push_back(std::make_unique<t34USA>());
        break;
      }
      case 1:
      {
        tanks.push_back(std::make_unique<t34USSR>());
        break;
      }
    }
  }

  for(int i = 0; i < size; ++i)
  {
    auto &obj = tanks[i];
    obj.get()->draw();
  }
}
Здесь процесс создания объектов и добавление в массив объектов находится в одном месте. Если же нам нужно перед созданием объекта провести определенные манипуляции с файлами, может быть что-то получить от сервера, и т.д. то создание объектов, загрузка данных, и т. д. находится в одном месте, что очень плохо сказывается на гибкости системы.

Классическая реализация:
#include <iostream>
using namespace std;
struct baseTank
{
  virtual void draw() = 0;
  virtual ~baseTank() = default;
};

struct t34USA : baseTank
{
  void draw() override { std::cout << "I am heavy tank T34(USA)" << std::endl; }
};

struct t34USSR : baseTank
{
  void draw() override { std::cout << "I am T-34(USSR)" << std::endl; }
};

struct TankFactory
{
  virtual std::unique_ptr<baseTank> make_tank() = 0;
  virtual ~TankFactory() = default;
};

struct T34USA_Factory : TankFactory
{
  std::unique_ptr<baseTank> make_tank() override
  {
    return std::make_unique<t34USA>();
  }
};

struct T34USSR_Factory : TankFactory
{
  std::unique_ptr<baseTank> make_tank() override
  {
    return std::make_unique<t34USSR>();
  }
};

void example()
{
  std::srand(time(0));

  using uptr = std::unique_ptr<baseTank>;

  std::vector<uptr> tanks;

  int size = (std::rand() % 10) + 1;
  for(int i = 0; i < size; ++i)
  {
    int rval = std::rand() % 2;
    switch(rval)
    {
      case 0:
      {
        static T34USA_Factory t34;
        tanks.push_back(t34.make_tank());
        break;
      }
      case 1:
      {
        static T34USSR_Factory t34;
        tanks.push_back(t34.make_tank());
        break;
      }
    }
  }

  for(int i = 0; i < size; ++i)
  {
    auto &obj = tanks[i];
    obj.get()->draw();
  }
}

Реализация с параметризированным фабричным методом, пример 1:
#include <iostream>
using namespace std;
enum class eTankType
{
  T34_USA
 ,T34_USSR
};

struct baseTank
{
  virtual void draw() = 0;
  virtual ~baseTank() = default;

  // fabric method
  static std::unique_ptr<baseTank> make_tank(eTankType aType);
};

struct t34USA : baseTank
{
  void draw() override { std::cout << "I am heavy tank T34(USA)" << std::endl; }
};

struct t34USSR : baseTank
{
  void draw() override { std::cout << "I am T-34(USSR)" << std::endl; }
};

std::unique_ptr<baseTank> baseTank::make_tank(eTankType aType)
{
  switch(aType)
  {
    case eTankType::T34_USA:
      return std::make_unique<t34USA>();
    case eTankType::T34_USSR:
      return std::make_unique<t34USSR>();
  }
}

void example()
{
  std::srand(time(0));

  using uptr = std::unique_ptr<baseTank>;

  std::vector<uptr> tanks;

  int size = (std::rand() % 10) + 1;
  for(int i = 0; i < size; ++i)
  {
    int rval = std::rand() % 2;
    switch(rval)
    {
      case 0:
      {
        tanks.push_back(baseTank::make_tank(eTankType::T34_USA));
        break;
      }
      case 1:
      {
        tanks.push_back(baseTank::make_tank(eTankType::T34_USSR));
        break;
      }
    }
  }

  for(int i = 0; i < size; ++i)
  {
    auto &obj = tanks[i];
    obj.get()->draw();
  }
}

Реализация с параметризированным фабричным методом, пример 2:
#include <iostream>
using namespace std;
enum class eTankType
{
  T34_USA
 ,T34_USSR
};

// forward declaration
struct t34USA;
struct t34USSR;

struct baseTank
{
  virtual void draw() = 0;
  virtual ~baseTank() = default;

  // fabric method
  static std::unique_ptr<baseTank> make_t34USA();
  static std::unique_ptr<baseTank> make_t34USSR();
};

struct t34USA : baseTank
{
  void draw() override { std::cout << "I am heavy tank T34(USA)" << std::endl; }
};

struct t34USSR : baseTank
{
  void draw() override { std::cout << "I am T-34(USSR)" << std::endl; }
};

std::unique_ptr<baseTank> baseTank::make_t34USA()
{
  return std::make_unique<t34USA>();
}

std::unique_ptr<baseTank> baseTank::make_t34USSR()
{
  return std::make_unique<t34USSR>();
}

void example()
{
  std::srand(time(0));

  using uptr = std::unique_ptr<baseTank>;

  std::vector<uptr> tanks;

  int size = (std::rand() % 10) + 1;
  for(int i = 0; i < size; ++i)
  {
    int rval = std::rand() % 2;
    switch(rval)
    {
      case 0:
      {
        tanks.push_back(baseTank::make_t34USA());
        break;
      }
      case 1:
      {
        tanks.push_back(baseTank::make_t34USSR());
        break;
      }
    }
  }

  for(int i = 0; i < size; ++i)
  {
    auto &obj = tanks[i];
    obj.get()->draw();
  }
}

Реализация с параметризированным фабричным методом, пример 3:
#include <iostream>
using namespace std;
struct baseTank
{
  virtual void draw() = 0;
  virtual ~baseTank() = default;
};

struct t34USA : baseTank
{
  void draw() override { std::cout << "I am heavy tank T34(USA)" << std::endl; }
};

struct t34USSR : baseTank
{
  void draw() override { std::cout << "I am T-34(USSR)" << std::endl; }
};

struct TankFactory
{
  enum class eTankType
  {
    T34_USA
   ,T34_USSR
  };

  static std::unique_ptr<baseTank> make_tank(eTankType aType)
  {
    switch(aType)
    {
      case eTankType::T34_USA:
        return std::make_unique<t34USA>();
      case eTankType::T34_USSR:
        return std::make_unique<t34USSR>();
    }
  }
};

void example()
{
  std::srand(time(0));

  using uptr = std::unique_ptr<baseTank>;

  std::vector<uptr> tanks;

  int size = (std::rand() % 10) + 1;
  for(int i = 0; i < size; ++i)
  {
    int rval = std::rand() % 2;
    switch(rval)
    {
      case 0:
      {
        tanks.push_back(TankFactory::make_tank(TankFactory::eTankType::T34_USA));
        break;
      }
      case 1:
      {
        tanks.push_back(TankFactory::make_tank(TankFactory::eTankType::T34_USSR));
        break;
      }
    }
  }

  for(int i = 0; i < size; ++i)
  {
    auto &obj = tanks[i];
    obj.get()->draw();
  }
}
В первом и втором примере базовый класс обладает знанием о всех производных от него классах, что не очень хорошо, а вот в третьем примере, создана специальная фабрика. Базовый класс не знает о том какие есть и какие будут наследники. Соответственно фабрика может находиться совсем в другом модуле, что дает больше гибкости чем в первых двух примерах.

ВОЗМОЖНЫ ДОПОЛНЕНИЯ





Комментарии