Сопоставление типов в фабрике объектов

Предположим, что у меня есть два набора связанных типов, например Animals и их Offspring:

/* Animal types */
struct Animal
{
  virtual string getType() const = 0;
};

struct Cat : public Animal
{
  virtual string getType() const { return "Cat"; }
};

struct Dog : public Animal
{
  virtual string getType() const { return "Dog"; }
};


/* Offspring types */
struct Offspring
{
  virtual string getType() const = 0;
};

struct Kitten : public Offspring
{
  virtual string getType() const { return "Kitten"; }
};

struct Puppy : public Offspring
{
  virtual string getType() const { return "Puppy"; }
};

Я пытаюсь реализовать фабрику, которая, учитывая Animal, вернет объект связанного типа Offspring (например, если Animal на самом деле является Dog, фабрика вернет Puppy).

Моя первая попытка реализовать такую ​​фабрику выглядит так:

// First attempt at OffspringFactory
class OffspringFactory1
{
  static Offspring* createKitten() { return new Kitten(); }
  static Offspring* createPuppy()  { return new Puppy();  }

public:
  // Create an Offspring according to the Animal type
  static Offspring* getOffspring(const Animal& a)
  {
    // Static mapping of Animal types to Offspring factory functions
    static map<string, Offspring* (*)()> factoryMap;
    if (factoryMap.empty())
    {
      factoryMap["Dog"] = &createPuppy;
      factoryMap["Cat"] = &createKitten;
    }

    // Lookup our Offspring factory function
    map<string, Offspring* (*)()>::const_iterator fnIt = factoryMap.find(a.getType());
    if (fnIt != factoryMap.end())
      return fnIt->second();
    else
      throw "Bad animal type";
  }
};

Он отлично работает, но я прибегал к отображению на основе строк, а не к чему-то чисто основанному на типах. Пытаясь перейти к реализации, основанной на типах, я пришел к следующему:

// Second attempt at OffspringFactory
class OffspringFactory2
{
  // Mapping Animal types to Offspring types
  template<typename TAnimal> struct OffspringMapper;

  template<>
  struct OffspringMapper<Cat> {
    typedef Kitten offspring_type;
  };

  template<>
  struct OffspringMapper<Dog> {
    typedef Puppy offspring_type;
  };

  // Factory method
  template<typename TAnimal>
    static Offspring* create() { return new OffspringMapper<TAnimal>::offspring_type(); }

public:
  // Create an Offspring according to the Animal type
  static Offspring* getOffspring(const Animal& a)
  {
    // Static mapping of Animal type strings to Offspring factory functions
    static map<string, Offspring* (*)()> factoryMap;
    if (factoryMap.empty())
    {
      factoryMap["Dog"] = &create<Dog>;
      factoryMap["Cat"] = &create<Cat>;
    }

    // Lookup our Offspring factory function
    map<string, Offspring* (*)()>::const_iterator fnIt = factoryMap.find(a.getType());
    if (fnIt != factoryMap.end())
      return fnIt->second();
    else
      throw "Bad animal type";
  }
};

Честно говоря, я не уверен, что что-то здесь улучшил: у меня все еще есть сопоставление строк, а также еще несколько строк менее читаемого кода...

Есть ли какие-то преимущества во второй реализации по сравнению с первой, и можно ли как-нибудь избавиться от этой карты?


person atkins    schedule 21.10.2011    source источник
comment
я не совсем уверен, чего вы пытаетесь достичь? есть ли какая-то реальная жизненная проблема, которую вы пытаетесь решить?   -  person AndersK    schedule 21.10.2011
comment
Вы можете добавить static Offspring* Animal::createOffspring() =0;? Это сделало бы это ДЕЙСТВИТЕЛЬНО легким. В противном случае вам придется просто заменить свои строки перечислением.   -  person Mooing Duck    schedule 21.10.2011
comment
@MooingDuck Я думаю, вы имели в виду virtual, а не static в своем комментарии.   -  person Kurt Stutsman    schedule 21.10.2011
comment
Да, да, я сделал. virtual Offspring* Animal::createOffspring() =0;   -  person Mooing Duck    schedule 21.10.2011
comment
@MooingDuck Спасибо за ваше предложение. Согласитесь, это было бы очень простое решение. К сожалению, я не упомянул ключевой факт в своем исходном посте, а именно то, что я не могу напрямую изменять классы Animal или Offspring (они живут в сторонних библиотеках).   -  person atkins    schedule 22.10.2011
comment
@АндерсК. В моей реальной задаче классы Animal — это разные типы данных финансового рынка, а Offspring — финансовые продукты. Например. Я могу получить объект данных, представляющий цену акции, и мне нужно создать продукт, представляющий позицию в этой акции.   -  person atkins    schedule 22.10.2011
comment
К сожалению, это сильно все меняет   -  person Mooing Duck    schedule 22.10.2011
comment
Есть много вещей, которые вы можете сделать, чтобы улучшить производительность, но они непросты и дают минимальную выгоду. Если это не является узким местом, используйте свой первый фрагмент кода.   -  person Mooing Duck    schedule 22.10.2011


Ответы (1)


Это похоже на классический случай двойной отправки. Одним из шаблонов для решения этой проблемы в C++ является шаблон посетителя.

class Offspring;
class OffspringFactory;

class Animal {
public:
    // ... rest of Animal class ...

    virtual Offspring* acceptOffspringFactory(OffspringFactory& factory)const = 0;
};

class OffspringFactory {
public:
    Offspring* createCatOffspring()
    {
        return new Kitten;
    }

    // ... one createXOffspring() for each type of Animal

    Offspring* getOffspring(const Animal& a)
    {
        return a.acceptOffspringFactory(*this);
    }
};

Offspring* Cat::acceptOffspringFactory(OffspringFactory& factory)const
{
    return factory.createCatOffspring();
}

// etc for rest of Animal classes

Теперь, когда я снова смотрю на вашу проблему, вы не указываете, что фабрика является абстрактной, поэтому на самом деле вы могли бы полностью избавиться от фабрики, если бы вы могли добавить метод, подобный упомянутому @MooingDuck.

person Kurt Stutsman    schedule 21.10.2011
comment
Я не понимаю, откуда взялась двойная отправка. Фабричный метод полиморфен для одного типа: класса животных. - person André Caron; 21.10.2011
comment
@AndréCaron Действительно, именно поэтому я пересмотрел свой ответ с последующим заявлением. Я написал исходный код, ожидая, что OffspringFactory будет абстрактной фабрикой. - person Kurt Stutsman; 21.10.2011
comment
@KurtS спасибо за это предложение, я чувствую, что могу что-то использовать здесь, если немного подумаю с моей стороны. Основная проблема (которую я не очень понимал, пока не прочитал ответы здесь) заключается в том, что я не могу напрямую изменять классы Animal и Offspring, поскольку они взяты из сторонних библиотек. - person atkins; 22.10.2011