Jump to content

  • Log In with Google      Sign In   
  • Create Account

We need your feedback on a survey! Each completed response supports our community and gives you a chance to win a $25 Amazon gift card!


Like
1Likes
Dislike

Creating a Generic Object Factory

By Robert Geiman | Published May 19 2004 03:41 PM in General Programming

object class factory shape_factory shape create new constructor shapefactory
If you find this article contains errors or problems rendering it unreadable (missing images or files, mangled code, improper text formatting, etc) please contact the editor so corrections can be made. Thank you for helping us improve this resource

The Introduction

Object factories are one of those extremely useful but often times overlooked constructs in programming. To put it simply, object factories are like virtual functions but instead of dynamically choosing which function gets executed at runtime, object factories dynamically choose which class gets instantiated at runtime.

If you don't have a good understanding of object factories (sometimes also called pluggable factories) then I highly recommend reading Industrial Strength Pluggable Factories and Why Pluggable Factories Rock My Multiplayer World before continuing this article.

All set? Good, then let's get started.

The Need

We already stated that object factories delay the choice of which object is created until runtime. Before we go any further, it might be beneficial to list some examples of when need to delay the choice until runtime. Here are just a few:
  • Scripting language support – The game must decide which command class to create and execute based on text commands entered by the user.
     
  • Serialization - One type of serialization would be communicating via TCP/IP. The receiving side must be able to dynamically create the proper message class depending on the type of message it has just received.
     
  • Executing commands - Many games allow users to dynamically rebind keys to other commands. Pressing the 'A' key should be able to create and execute the command of the user's choice.
     
  • To decreasing class dependencies – By not hard-coding the which class to instantiate we can greatly reduce class dependencies, as classes no longer need to know about other classes in order to create them. This can result in greatly decreased compile times.
There are of course many other instances where object factories are useful, but I won't bore you with them all. Instead, let's get on to something more exciting.

The Implementation

OK, now that we know when object factories are useful it's time to figure out how to actually implement one. Below we have an object factory in its most simplistic form.

class ShapeFactory
{
public:
  Shape *Create(int shape)
  {
	if (shape == SQUARE)
	  return new Square;
	else if (shape == CIRCLE)
	  return new Circle;
	else if (shape == TRIANGLE)
	  return new Triangle;
  }
};

Not much to it, really. The ShapeFactory class has a Create method which allows us to create a Square, Circle, or Triangle class simply by passing the appropriate unique identifier, in this case an integer. This integer value could be hard-coded or passed in from an outside source such as a TCP/IP stream, a script file, or from the player himself.

The syntax to use our sample object factory class is simple enough:

ShapeFactory shape_factory;

Shape *shape1 = shape_factory.Create(TRIANGLE);
Shape *shape2 = shape_factory.Create(SQUARE);

Improving our object factory

While this implementation is a good start, there are quite a few ways to improve it. The most noticeable flaw is that we must edit the ShapeFactory class to add the necessary support for every new shape class we add to our project. This can be both time consuming and error prone.

A better solution over hard-coding our shapes would be to dynamically 'register' shapes with the ShapeFactory class. Listing 1 shows an object factory class that does just that.

Ok, let's inspect this code closely. First, notice we no longer create the shape instances inside our ShapeFactory class. Instead, we'll use function pointers to do this for us. These function pointers are passed to the ShapeFactory via the Register method and are internally stored in a map container for quick access. When we ask to create an instance of a class the ShapeFactory class retrieves the function pointer that is associated with the unique identifier, and executes that function to create the new instance. Pretty simple, really. The end result is that we no longer have to edit our ShapeFactory class to add support for new shapes!

The syntax for using this new implementation has changed from the sample implementation above. Here's an example of how to use the object factory in this new version:

Shape *CreateTriangle()
{
  return new Triangle;
}

Shape *CreateSquare()
{
  return new Square;
}
  …
ShapeFactory shape_factory;

shape_factory.Register(TRIANGLE, &CreateTriangle);
shape_factory.Register(SQUARE, &CreateSquare);

Shape *shape1 = shape_factory.Create(TRIANGLE);
Shape *shape2 = shape_factory.Create(SQUARE);

Right away you'll notice the two new functions called CreateTriangle and CreateSquare. The sole job of CreateTriangle and CreateSquare is to simply create and return a new instance of the Triangle and Square class respectively.

The next difference is the addition of two Register calls. Since classes are no longer hard-coded in the ShapeFactory class they must now be dynamically registered via this method. They can also be dynamically unregistered by calling the Unregister method.

A generic implementation

While we've improved our object factory there is another modification we could make that would greatly improve our design: make the object factory class generic enough to be used with any base class. This would allow us to create just one object factory class that could be used to create shapes, commands, monsters, or anything other class we may need it for.

This may seem like a tall order at first, but thanks the power of C++ templates it's not all that hard to do. Listing 2 shows how we implement this.

There's not much to it, really. However, you may have noticed a new templated function called CreateObject. I figured since we were already on board the template train we might as well make our life easier and create a templated version of those functions we used in the previous implementation.

Because of the changes made above, the syntax required to use our object factory has once again changed a bit, so let's look at the new syntax closely:

ObjectFactory<Shape> shape_factory;

shape_factory.Register(TRIANGLE, &CreateObject<Shape, Triangle>);
shape_factory.Register(SQUARE, &CreateObject<Shape, Square>);

Shape *shape1 = shape_factory.Create(TRIANGLE);
Shape *shape2 = shape_factory.Create(SQUARE);

The first thing you should notice is we now include a new template parameter when we create our object factory class instance. This required template parameter specifies the base class our object factory will return, in this case the base Shape class.

The other syntax change is in the Register method. Instead of hand-writing new functions for each class we register, we can now just pass the address of the templated CreateObject function instead. The syntax is more verbose, but it saves us the time of hand-writing new functions for every class we wish to register.

The end result is that we now have an object factory class that can be used to create any type of class, and we are no longer required to write any helper classes or functions to accomplish this. Not too shabby.

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;

The Conclusion

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;







Comments

Note: Please offer only positive, constructive comments - we are looking to promote a positive atmosphere where collaboration is valued above all else.




PARTNERS