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

Да предположим, че имам два набора от свързани типове, например 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;? Това би направило това НАИСТИНА лесно. В противен случай ще трябва просто да замените вашите низове с enum.   -  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
@AndersK. В моя проблем от реалния свят класовете 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