Реализация шаблона фабрики C ++ - пример, вопросы и проблемы - PullRequest
2 голосов
/ 02 июня 2019

В последнее время я пытался лучше понять, как использовать Factory Pattern в C ++.

Я ознакомился со следующими ресурсами:

Как реализовать фабрикушаблон метода в C ++ правильно

https://www.geeksforgeeks.org/design-patterns-set-2-factory-method/

https://sourcemaking.com/design_patterns/factory_method/cpp/1

https://www.codeproject.com/Articles/363338/Factory-Pattern-in-Cplusplus

https://www.bogotobogo.com/DesignPatterns/factorymethod.php

https://gist.github.com/pazdera/1099562

https://en.wikibooks.org/wiki/C%2B%2B_Programming/Code/Design_Patterns

Прежде чем кто-то пометит это как "не вопрос", у меня есть несколько явных вопросов, которые я скоро получу.Но чтобы прояснить мое понимание, сначала я хотел бы начать с примера.Это лучший пример, который я смог сделать из информации, представленной в приведенных выше ссылках (наилучшее значение иллюстрирует шаблон фабрики, но он настолько прост, насколько это возможно, поэтому новички в этом шаблоне могут ясно видеть связанные с этим проблемы):

// FactoryPattern.cpp

#include <iostream>
#include <vector>

enum AnimalSpecies { dog, cat };


class Animal
{
public:
  virtual void makeSound() = 0;
};

class Dog : public Animal
{
public:
  void makeSound() { std::cout << "woof" << "\n\n"; }
};

class Cat : public Animal
{
public:
  void makeSound() { std::cout << "meow" << "\n\n"; }
};

class AnimalFactory
{
public:
  static Animal* makeAnimal(AnimalSpecies animalSpecies);
};

Animal* AnimalFactory::makeAnimal(AnimalSpecies animalSpecies)
{
  if (animalSpecies == AnimalSpecies::dog)
  {
    return(new Dog());
  }
  else if (animalSpecies == AnimalSpecies::cat)
  {
    return(new Cat());
  }
  else
  {
    std::cout << "error in AnimalFactory::makeAnimal(), animalSpecies = " << animalSpecies << " does not seem to be valid" << "\n\n";
    return(nullptr);
  }
}

int main(void)
{
  std::vector<Animal*> animals;

  animals.push_back(AnimalFactory::makeAnimal(AnimalSpecies::dog));
  animals.push_back(AnimalFactory::makeAnimal(AnimalSpecies::cat));

  for (auto &animal : animals)
  {
    animal->makeSound();
  }

  for (auto& animal : animals)
  {
    delete(animal);
  }

  return(0);
}

Вывод этой программы (как и предполагалось):

woof

meow

Вот мои вопросы на данный момент:

1) Некоторые из прочитанных мною наФабричный шаблон привел меня к убеждению, что устранение утверждений является частью выгоды.Вышеприведенный пример не подходит в этом отношении, поскольку в AnimalFactory::makeAnimal() для определения вида необходимо использовать if-else.Есть ли способ реорганизовать это для удаления операторов if?

2) В последнее время с различными изменениями и улучшениями в C ++ 14/17/20 многие из нас уходят от указателей, чтобы избежать возможности утечек памяти,Есть ли способ изменить это так, чтобы не использовать указатели?

3) В чем преимущество Factory Pattern?т.е. в приведенном выше примере почему бы не опустить класс AnimalFactory и просто сделать main() следующим образом:

int main(void)
{
  Dog dog;
  dog.makeSound();

  Cat cat;
  cat.makeSound();

  return(0);
}

Ответы [ 2 ]

3 голосов
/ 02 июня 2019

заставил меня поверить, что устранение утверждений является частью выгоды.

ИМХО, настоящая сила фабричного паттерна заключается в виртуализации фабричного паттерна:

class AnimalFactoryInterface
{
  public:
    virtual Animal* makeAnimal() = 0;
};

class CatFactory : public AnimalFactoryInterface
{
  public:
    Animal* makeAnimal() override { return new Cat(); }
};

Это виртуализирует, как создавать новых животных, что невозможно сделать иначе.

Конструктор оператора switch полезен для создания животных из конфигурационного файла, но здесь это не совсем так. Это помогает при создании универсального класса Breeder, который принимает, как создавать универсальных животных.

2) В последнее время с различными изменениями и улучшениями в C ++ 14/17/20 многие из нас уходят от указателей во избежание утечек памяти. Есть ли способ изменить это так, чтобы не использовать указатели?

Возврат умных указателей (std::unique_ptr<Animal>).

3) В чем преимущество фабричной модели? то есть в приведенном выше примере почему бы не опустить класс AnimalFactory и просто сделать main () следующим образом:

Сначала это легко сделать

class Breeder {
 public:
   Breeder(std::unique_ptr<AnimalFactoryInterface> animal_factory);
   void Breed() { animals_.push_back(animal_factory_->Create()); }
   // ... more stuff
 private:
   std::unique_ptr<AnimalFactoryInterface> animal_factory_;
   std::vector<std::unique_ptr<Animal>> animals_;
};

Вторым является Внедрение зависимостей .

Мы также должны уточнить «фабричный шаблон».

В этом ответе я описал Абстрактный шаблон фабрики , то есть использование фабричного интерфейса для создания объектов.

Для создания объектов используется один метод с оператором switch - Factory Method Pattern . У него определенно есть свои утилиты, но я всегда находил его слишком очевидным, чтобы слишком привязывать его к полному «шаблону проектирования». Но для этого вопроса и для вопроса о преимуществах важно то, что если все, что вы делаете, - это просто делаете серию объектов, то вы правы - вам определенно не нужен фабричный класс, и вы даже не нужен специальный метод, если все, что вы делаете, - это создание классов один раз. Практическое правило для любого программирования - делать самое простое, что работает.

1 голос
/ 02 июня 2019

Re 1 + 3: Вы можете удалить if-else, либо заменив его оператором switch-case (эквивалентным, но более явным), либо создав статическую карту из типа enum в указатель на конструктор.Однако: Factory Pattern не предназначен для устранения всех операторов if;он просто инкапсулирует логику создания в одном месте, поэтому он не будет повторяться каждый раз, когда вам нужно создать новое животное из типа enum.

Преимущество использования Фабрики состоит в том, чтобы скрыть сложность созданияобъект вне иерархии классов, когда логика не так проста, как вызов конструктора.Например:

  1. Здесь задействовано много настроек, поэтому вы хотите создать объект, который создает все объекты с одинаковыми настройками;например, фабрика Animals, которая создает всех животных с флагом is_noisy, установленным в false.
  2. Логика создания объекта не тривиальна, например, создание животного из строки конфигурации в формате JSON.
  3. Есть дополнительные глобальные аспекты, такие как подсчет всех выделенных животных.
...