Implementing Object Factories

We are now armed with a little XML knowledge but before we move forward, we are going to take a look at Object Factories. An object factory is a class that is tasked with the creation of our objects. Essentially, we tell the factory the object we would like it to create and it goes ahead and creates a new instance of that object and then returns it. We can start by looking at a rudimentary implementation:

GameObject* GameObjectFactory::createGameObject(ID id)
{
  switch(id)
  {
    case "PLAYER":
      return new Player();
    break;
    
    case "ENEMY":
      return new Enemy();
    break;
    
    // lots more object types 
  }
}

This function is very simple. We pass in an ID for the object and the factory uses a big switch statement to look it up and return the correct object. Not a terrible solution but also not a particularly good one, as the factory will need to know about each type it needs to create and maintaining the switch statement for many different objects would be extremely tedious. Just as when we covered looping through game objects in Chapter 3, Working with Game Objects, we want this factory not to care about which type we ask for. It shouldn't need to know all of the specific types we want it to create. Luckily this is something that we can definitely achieve.

Using Distributed Factories

Through the use of Distributed Factories we can make a generic object factory that will create any of our types. Distributed factories allow us to dynamically maintain the types of objects we want our factory to create, rather than hard code them into a function (like in the preceding simple example). The approach we will take is to have the factory contain std::map that maps a string (the type of our object) to a small class called Creator whose only purpose is the creation of a specific object. We will register a new type with the factory using a function that takes a string (the ID) and a Creator class and adds them to the factory's map. We are going to start with the base class for all the Creator types. Create GameObjectFactory.h and declare this class at the top of the file.

#include <string>
#include <map>
#include "GameObject.h"

class BaseCreator
{
  public:

  virtual GameObject* createGameObject() const = 0;
  virtual ~BaseCreator() {}
};

We can now go ahead and create the rest of our factory and then go through it piece by piece.

class GameObjectFactory
{
  public:

  bool registerType(std::string typeID, BaseCreator* pCreator)
  {
    std::map<std::string, BaseCreator*>::iterator it = 
    m_creators.find(typeID);

    // if the type is already registered, do nothing
    if(it != m_creators.end())
    {
      delete pCreator;
      return false;
    }

    m_creators[typeID] = pCreator;

    return true;
  }

  GameObject* create(std::string typeID)
  {
    std::map<std::string, BaseCreator*>::iterator it = 
    m_creators.find(typeID);

    if(it == m_creators.end())
    {
      std::cout << "could not find type: " << typeID << "
";
      return NULL;
    }

    BaseCreator* pCreator = (*it).second;
    return pCreator->createGameObject();
  }

  private:

  std::map<std::string, BaseCreator*> m_creators;

};

This is quite a small class but it is actually very powerful. We will cover each part separately starting with std::map m_creators.

std::map<std::string, BaseCreator*> m_creators;

This map holds the important elements of our factory, the functions of the class essentially either add or remove from this map. This becomes apparent when we look at the registerType function:

bool registerType(std::string typeID, BaseCreator* pCreator)

This function takes the ID we want to associate the object type with (as a string), and the creator object for that class. The function then attempts to find the type using the std::mapfind function:

std::map<std::string, BaseCreator*>::iterator it = m_creators.find(typeID);

If the type is found then it is already registered. The function then deletes the passed in pointer and returns false:

if(it != m_creators.end())
{
  delete pCreator;
  return false;
}

If the type is not already registered then it can be assigned to the map and then true is returned:

m_creators[typeID] = pCreator;
return true;
}

As you can see, the registerType function is actually very simple; it is just a way to add types to the map. The create function is very similar:

GameObject* create(std::string typeID)
{
  std::map<std::string, BaseCreator*>::iterator it = 
  m_creators.find(typeID);

  if(it == m_creators.end())
  {
    std::cout << "could not find type: " << typeID << "
";
    return 0;
  }

  BaseCreator* pCreator = (*it).second;
  return pCreator->createGameObject();
}

The function looks for the type in the same way as registerType does, but this time it checks whether the type was not found (as opposed to found). If the type is not found we return 0, and if the type is found then we use the Creator object for that type to return a new instance of it as a pointer to GameObject.

It is worth noting that the GameObjectFactory class should probably be a singleton. We won't cover how to make it a singleton as this has been covered in the previous chapters. Try implementing it yourself or see how it is implemented in the source code download.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.122.244