Changes, changes, and more changes
While we do have a fairly generic object factory implementation, we have been ignoring one aspect of our object factory class that has been annoying me greatly. We assume we'll always want to use an integer as the data type of the unique identifier, and this may not always be the case.
For instance, if we wish to allow users of our game to execute commands via a console or scripting language, it would be easier to map the text commands directly to the command classes that carry out those commands. Since we've already templated the base class it should be a relatively simple matter to do the same with the unique identifier type.
We'll also see if we can make the syntax for registering classes a bit cleaner.
Lastly, we'll add code to allow users of our class to iterate through all registered unique identifiers. This can be useful if they wish to display to the user valid unique identifiers.
Let's see how what the object factory looks like in Listing 3.
The change to our interface is simple enough, as you'll see below:
ObjectFactory<Shape, std::string> shape_factory; shape_factory.Register<Triangle>("triangle"); shape_factory.Register<Square>("square"); Shape *shape1 = shape_factory.Create("triangle"); Shape *shape2 = shape_factory.Create("square");
First, we added a new required template parameter for the unique identifier type when we create our object factory class instance.
Second, notice that we register classes simply by passing the class name as a template parameter and passing the unique identifier as a function parameter. This is a much-improved way to register classes compared to our previous versions.
The third, and last, change we made is to allow users to iterate through our registered classes. This syntax is the same as iterating through any STL container so I won't go over examples of its use here.
The finishing touches
Despite all the improvements we've just made, there is one glaring omission I was waiting until the end of this article to address: allowing the user to pass constructor parameters to the object factory.
Modifying the object factory class to handle constructor parameters correctly is no small change, as it requires some heavy use of partial template specialization. Listing 4 shows an example of how this can be accomplished.
Looking at the example source you can see that we need to create a new variation of our object factory for every constructor parameter we want to support. In other words, if we support a possible 15 constructor parameters then we must create 15 variations of our object factory class.
Since this is a very time consuming and error prone process the source code attached to this article uses some macro magic so we only need to write our object factory one time, and still support as many constructor parameters as we'd like.
The syntax for using this final implementation of the object factory looks like this:
ObjectFactory<Shape *(int), std::string> shape_factory; shape_factory.Register<Triangle>("triangle"); shape_factory.Register<Square>("square"); Shape *shape1 = shape_factory.Create("triangle", 10); Shape *shape2 = shape_factory.Create("square", 20);
Notice the syntax used to create the shape factory instance. We pass 'Shape *(int)', which is basically the signature of the constructor we want the shape factory to use. It basically says the constructor takes one parameter, an integer, and returns a 'Shape *' object. Because we specified 'Shape *(int) ' as the constructor signature we are now required to pass an integer value to the Create method.
If we didn't want to pass any constructor parameters, we'd define the object factory like this:
ObjectFactory<Shape *(), std::string> shape_factory;
We've discussed when object factories are useful, and we've managed to create a generic and simple to use object factory implementation.
The source attached to this article contains our final implementation of the object factory, as well as two example programs that show how to use the object factory.
Important Note: This code has been tested on Visual C++ 7.1. However, because the final implementation of the object factory uses partial template specialization some non-compliant compilers, such as Visual C++ 6.0, will not be able to compile this code.
Because of this, the source attached to this article also contains a special version of the object factory written specifically for Visual C++ 6.0. To get around the lack of partial template specialization support on Visual C++ 6.0 we must have a separate object factory class for each constructor parameter. Also, because of Visual C++ 6.0's lack of 'explicit template argument specification for member functions' support we must use a hack on the Register function. Below is an example of how to use the object factory in Visual C++ 6.0:
ObjectFactory1<Shape, int, std::string> shape_factory; shape_factory.Register("triangle", Type2Type<Triangle>()); shape_factory.Register("square", Type2Type<Square>()); Shape *shape1 = shape_factory.Create("triangle", 10); Shape *shape2 = shape_factory.Create("square", 20);
Notice we append a number to the ObjectFactory class name, which specifies the number of constructor parameters we wish to use. We then must list each constructor parameter as a separate template parameter. If we didn't want to specify any constructor parameters, we'd define the object factory like this:
ObjectFactory0<Shape, std::string> shape_factory;