The Templated Factory

Since we need to create different versions of the same algorithms using different type, we should again make use of C++ templates. This will allow us to write the code one time, and reuse it for any factory type we need.

First we need to factor out the types that are different. If you look at all three classes, you will see that three types are different. The enumeration type, the builder type, and the return type for the Build method is different in all three classes. If we make those template parameters, we can reuse the same code instead of recreating the same class three times. Here is how we should refactor our code:

//M5Factory.h 
template<typename EnumType,
typename BuilderType,
typename ReturnType>
class M5Factory
{
public:
~M5Factory(void);
void AddBuilder(EnumType type, BuilderType* pBuilder);
void RemoveBuilder(EnumType type);
ReturnType* Build(EnumType type);
void ClearBuilders(void);
private:
typedef std::unordered_map<EnumType, BuilderType*> BuilderMap;
typedef typename BuilderMap::iterator MapItor;
BuilderMap m_builderMap;
};

Notice that our class is now a template class with three template parameters, EnumType, BuilderType, and ReturnType. Instead of any specific types such as M5StageTypes, we have used our template parameters. One change that confuses many people is this line:

typedef typename BuilderMap::iterator MapItor; 

In the original, non-templated M5StageFactory class, the compiler was able to look at the code BuilderMap::iterator and know for certain that iterator was a type inside BuilderMap. Now that we have a template class, the compiler can't be sure whether BuilderMap::iterator is a variable or a type, so we need to help the compiler by using the typename keyword to say that this is a type.

Since our factory is now a templated class, we should again put all functions implementations into the header file. In addition, each implementation must be marked as a template function. Here is an example of one of the Build methods:

//M5Factory.h 
template<typename EnumType,
typename BuilderType,
typename ReturnType>
ReturnType* M5Factory<EnumType,
BuilderType,
ReturnType>::Build(EnumType type)
{
MapItor itor = m_builderMap.find(type);
M5DEBUG_ASSERT(itor != m_builderMap.end(),
"Trying to use a Builder that doesn't exist");
return itor->second->Build();
}

Except for the change in the function signature, the Build function is exactly the same. This is true of AddBuilder, RemoveBuilder, and all functions of the class. As I said, by making the Dynamic Factory a template class, we can reuse the same code for our stage factory, our component factory, and our object factory. Since that is the case, we won't spend time on making the template factory. However, we still need to see how to use this new class. Let's look at our M5StageFactory class:

class M5StageManager 
{
public:
//Same as before
private:
//static M5StageFactory s_stageFactory; //Our Old Code
static M5Factory<M5StageTypes,
M5StageBuilder,
M5Stage> s_stageFactory;//Our new code

};

That is the only change we need to make to our M5StageFactory. Everything else will work the exact same way. The nice thing is that once we have our template factory completed, using a component factory is easy. Here is how we can use our component factory and object factory inside our M5ObjectManager class:

class M5ObjectManager 
{
public:
//See M5ObjectManager.h for details
private:
static M5Factory<M5ComponentTypes,
M5ComponentBuilder,
M5Component> s_componentFactory;
static M5Factory<M5ArcheTypes,
M5ObjectBuilder,
M5Object> s_ObjectFactory;
};

Reusing the code is simple once we have created the template version. We should have created that first, but most programmers have a hard time thinking about how to reuse a class until after the initial code has been written. I think it is easier and more natural to create the stage factory first, then refactor the code into a template class.

There is one more important thing to consider when using our factory: how to add our builders. For now, let's only consider the M5Factory inside the M5StageManager since we have seen that code already. Somewhere in our code, we must still instantiate our derived builders so we can add them to the M5StageManager. For example, we would need a function like this:

#include "M5StageManager.h" 
#include "M5StageTypes.h"
#include "M5StageBuilder.h"
#include "GamePlayStage.h" //Example Stage
#include "SplashStage.h" //Example Stage


void RegisterStages(void)
{
M5StageManager::AddStage(ST_GamePlayStage,
new M5StageTBuilder< GamePlayStage >() );
M5StageManager::AddStage(ST_SplashStage,
new M5StageTBuilder< SplashStage >() );
}

As you can see, this function is dependent on all stages in our game and is likely to change as we change our design. Unfortunately, this is as far as we can decouple our code. At some point, we need to instantiate derived classes. Even though this is necessary, later we will look at how to minimize the work involved in maintaining this code. In the case of the Mach5 Engine, this code is auto generated with a Windows batch file before the code is compiled. By auto generating our file, we reduce the chance of forgetting to add a stage, and also minimize the work when our code changes.

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

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