Отображение типов в фабрике объектов - PullRequest
0 голосов
/ 21 октября 2011

Предположим, у меня есть два набора связанных типов, например Animal s и их 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";
  }
};

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

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

1 Ответ

1 голос
/ 21 октября 2011

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

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.

...