tags:

views:

478

answers:

7

I am considering a factory function to create different classes in the same hierarchy. I understand that normally a factory is normally implemented as follows:

Person* Person::Create(string type, ...)
{
    // Student, Secretary and Professor are all derived classes of Person
    if ( type == "student" ) return new Student(...);
    if ( type == "secretary" ) return new Secretary(...);
    if ( type == "professor" ) return new Professor(...);
    return NULL;
}

I am trying to think of a way so that the process can be automated so that the various conditions do not need to be hard-coded.

So far the only way I can think of is using a map and the prototype pattern:

The map will hold the type string in the first element and a class instance (prototype) in the second:

std::map<string, Person> PersonClassMap;
// This may be do-able from a configuration file, I am not sure
PersonClassMap.insert(make_pair("student", Student(...)));
PersonClassMap.insert(make_pair("secondary", Secretary(...)));
PersonClassMap.insert(make_pair("professor", Professor(...)));

The function may look something like this:

Person* Person::Create(string type)
{
    map<string, Person>::iterator it = PersonClassMap.find(type) ;
    if( it != PersonClassMap.end() )
    {
        return new Person(it->second); // Use copy constructor to create a new class instance from the prototype.
    }
}

Unfortunately, the prototype method only works if you only want the class created by the factory to be identical every time, since it does not support arguments.

Does anybody know if it is possible to do it in a nice way, or am I stuck with the factory function?

A: 

I am not familar with c++ but in many langugaes there concepts of delegates or closures. Means that instead of mapping to instance, you map to the function(delegate, closure) that is responsible to create object.

Mike Chaliy
C++ doesn't have closures or anything remotely similar. There are method pointers, but they don't add anything to the solution.
PanCrit
@PanCrit, are you joking? Do you familar with abstract factory pattern? Factory with just single method could be good replacement for native delegates. And yes it awailable in c++.
Mike Chaliy
A: 

Well if you want a faster way to do it then using an enum and a switch statement will be many time faster than processing sequential if/else if statements ...

Goz
enum and switch may run faster, but it's far more work to maintain. I think suggesting maintainable, object-based solutions would be far more valuable.
PanCrit
+1  A: 

I usually build a factory method (or a factory object) when the clients will be providing some information about the object to be created, but they don't know what concrete class the result will be. The determination about how to express the interface to the factory depends completely on what information the clients have. It could be that they provide a string (program text to be parsed, for example), or a set of parameter values (number of dimensions and sizes if we're creating geometric objects in n-space). The factory method then examines the information and decides what kind of object to create or which more specific factory to call.

So the decision about what to build shouldn't be made by the caller; if she knows, then there's no reason for a factory. If the list of things to be built is open-ended, you might even have a registration protocol that allows specific implementations to provide both their construction method and a discriminator function that would allow the factory method to decide which method to call.

It very much depends on what information is necessary and sufficient to decide which kind of object to build.

PanCrit
A: 

If you look at your two implementations, logically they are identical. The first implementation is the same as your second if the recursive loop was unrolled. So really there is no advantage of your second implementation.

Regardless what you do you will need to list some where your types mapped to your constructors.

One way of doing it that can be useful is to have this map in a seperate xml file

<person>
   <type> student </type>
   <constructor> Student </type>
</person>
 ....

You can then read this xml file in to memory and use reflection to get back your constructor. For the given type. Given that you are using C++ however this will not be that simple as C++ does not have reflection as standard. You will have to look for an extension to provide you with reflection in C++.

But regardless, all these alternatives can not escape what you did in your original implementioan, ie: list the mapping from type to constructor and search the map.

hhafez
Your approach is way too much complicated for nothing...
Partial
way to completely miss the point! First of All I was replying to the OPs comment quote: "This may be do-able from a configuration file, I am not sure" I showed him a way in which it can be done. But my point which you clearly missed was that any of these approaches all boil down to his initial implementation which he was trying to find an alternative to!
hhafez
A: 

You can register a factory method (instead of the prebuilt element to copy). This will allow you to call the abstract factory with parameters that are passed to the concrete factory. The limitation here is that the set of parameters of all concrete factories must be the same.

typedef std::string discriminator;
typedef Base* (*creator)( type1, type2, type3 ); // concrete factory, in this case a free function
typedef std::map< discriminator, creator > concrete_map;
class Factory // abstract
{
public:
   void register_factory( discriminator d, creator c ) {
      factories_[ d ] = c;
   }
   Base* create( discriminator d, type1 a1, type2 a2, type3 a3 )
   {
      return (*(factories_[ d ]))( a1, a2, a3 );
   }
private:
   concrete_map factories_;
};

I have used free function creators to reduce the sample code, but you can define a concrete_factory type and use it instead of the 'creator' element above. Again, as you can see, you are limited to a fixed set of arguments in the factory 'create' method.

Each concrete factory can pass the arguments to the constructor of the given type:

Base* createDerived1( type1 a1, type2 a2, type3 a3 )
{
   return new Derived1( a1, a2, a3 );
}

This is more flexible than your approach as you can create instances that hold references to external objects (those can only be initialized during construction) or constant members, or objects that cannot be reset to a different state after construction in a more general wording.

David Rodríguez - dribeas
+1  A: 

I would add a pure abstract clone method to class Person (which definitely looks like it should be an abstract class, existing mainly for the sake of being subclassed -- if you need a concrete "none of the above" kind of Person it's best done via a separate concrete subclass OtherKindOfPerson, rather than as the base class itself):

virtual Person* clone() const = 0;

and override it in every concrete subclass, e.g. in Student, with a new that invokes the specific concrete subclass's copy ctor:

Person* clone() const { return new Student(*this); }

You also need to change the registry map to:

std::map<string, Person*> PersonClassMap;

[[You could use some smarter pointer than a plain old Person *, but as the map and all of its entries probably needs to survive as long as the process does, this is definitely not a big deal -- the main added value you might get from smarter pointers being smarter behavior upon destruction of the "pointer"!-)]]

Now, your factory function can simply end with:

return it->second->clone();

The changes are needed to avoid the "slicing" effect of using the base class's copy ctor on a subclass that has extra attributes, as well as preserve any virtual method's resolution.

Subclassing a concrete class to yield other concrete classes is a bad idea exactly because these effects can be tricky and a source of bugs (see Haahr's recommendation against it: he writes about Java, but the advice is also good for C++ and other languages [indeed I find his recommendation even more crucial in C++!].

Alex Martelli
A: 

You could make an enum of each type of person:

enum PersonType { student, secretary, professor };
Partial