Generic factory in C++ [duplicate]

2020-03-06 11:02发布

问题:

I'm working on a game and am trying to implement a smart way to create npc-objects in C++ from parsing a text file.

Currently this is hard coded in a Factory-object. Like this:

IActor * ActorFactory::create(string actortype, Room * r, string name, int hp)
{
    if(actortype == "Troll")
    {
        return new Troll(r, name, hp);
    }
    if (actortype == "Dragon")
    {
        return new Dragon(r, name, hp);
    }
    // ... and so on
    throw "Can't recognize type '"+actortype+"'.";
}

This is in my opinion a very ugly way to do it. Since it (among other things) breaks the Open/Closed principle.

I am schooled in Java, and in Java I would do something like having each IActor report it's class name and class type to the ActorFactory in the beginning of program execution. The factory would then store the relation in a map and can then easily look up what string maps to which object and it can then easily instantiate it.

Edit: I would also like to have the ability to call the constructor with a variable number/type of arguments.

How would this be done in C++? Can it be done?

回答1:

In C++, you would typically use the Abstract Factory design pattern.

The point is: "the decision about the type of actor to create should not be the responsibility of ActorFactory::create()." In your case, this method should not decide which class to instantiate based on a string but would rather rely on a type; this type is the actual factory class.

  1. Each actor class has its own factory class: TrollFactory, DragonFactory, etc. deriving from a base class ActorFactory2 (trailing 2 because ActoryFactory is already taken);

  2. Each specialized factory class implements a virtual create() method without parameter returning a pointer to a newly created actor class;

  3. If you need parameters to construct an actor, pass them to the factory object before creating the actor: pass them in the ctor and store them as member variables; create() will retrieve them later upon creation of the actor;

  4. This way, you can easily pass different arguments for different actors and your factory mechanism will be scalable (a step toward the Open/Closed principle);

  5. Now, ActorFactory::create() accepts a pointer to an object deriving from ActorFactory2 and calls the ActorFactory2::create() method: it will create the requested actor with appropriate arguments without switch statement.

    class ActorFactory2
    {
      string m_name; // Each IA actor has a name
      int m_hp;      // and some HP
    public:
      ActorFactory2( const string &p_name, int p_hp )
        : m_name( p_name ), m_hp( p_hp ) {}
      virtual IActor * create() const = 0;
    };
    
    class TrollFactory : public ActorFactory2
    {
      // No special argument needed for Troll
    public:
      TrollFactory( const string &p_name, int p_hp )
        : ActorFactory2( p_name, p_hp ) {}
      virtual IActor * create() const { return new Troll( m_name, m_hp ); }
    };
    
    class DragonFactory : public ActorFactory2
    {
      FlameType m_flame; // Need to indicate type of flame a dragon spits
    public:
      DragonFactory( const string &p_name, int p_hp, const FlameType &p_flame )
        : ActorFactory2( p_name, p_hp )
        , m_flame( p_flame ) {}
      virtual IActor * create() const { return new Dragon( m_name, m_hp, m_flame ); }
    };
    
    IActor * ActorFactory::create( const ActorFactory2 *factory )
    {
      return factory->create();
    }
    
    int main( int, char ** )
    {
      ActorFactory af;
      ...
      // Create a dragon with a factory class instead of a string
      ActorFactory2 *dragonFactory = new DragonFactory( "Fred", 100, RedFlames );
      IActor *actor = af.create( dragonFactory ); // Instead of af.create( "dragon", ... )
      delete dragonFactory;
    }
    


回答2:

I have answered in another SO question about C++ factories. Please see there if a flexible factory is of interest. I try to describe an old way from ET++ to use macros which has worked great for me.

ET++ was a project to port old MacApp to C++ and X11. In the effort of it Eric Gamma etc started to think about Design Patterns



回答3:

The specific term is: parameterized factory method and it is part of the factory method design pattern.

To use a generic factory, hold the classes in a map and access via a string. If your class names are usable, register the class to the factory with "typeid(MyClass).name() and return a copy of the class by providing a clone() member function.

However, for simple not to extensible factories, I use the approach from your question.

I can't answer your question about passing more variable parameters, but to deserialize, it is enough to pass the portion to the class and let it deserialize itself (as you already seem to do).



回答4:

You could use a map to store function pointers that'd return Actor*, with that being a pointer to the object being created. so then the code would just be

std::map<std::string,IActor* (*) (Room*,std::string,int)> constructorMap    
constructorMap["Troll"]=&TrollConstructor
//etc...
IACtor* ActorFactory::create(string actortype,Room* r,string name,int hp){
  return (*constructorMap[actortype])(r,name,hp);
}

(please excuse any possible screw-ups I made with the function pointers, they are not my strong point)