Jump to content
  • Advertisement
Sign in to follow this  
Timkin

Pluggable factories and object initialisation

This topic is 4027 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

So, I've got a pluggable factory that can construct objects within a given heirarchy, but I have an issue with initialising derived objects within this heirarchy. Here's the source code for my factory class
// Factory.h
#ifndef FACTORY_H
#define FACTORY_H

#include <map>
#include "ObjectTypes.h"

template <typename BASE_TYPE>
class Factory
{
  typedef std::map<OBJ_TYPE,Factory*> FactoryRegistry;
  static FactoryRegistry& Registry();
  virtual BASE_TYPE* Create() const = 0;
public:
  Factory(OBJ_TYPE type);
  static BASE_TYPE* Construct(OBJ_TYPE type);
};

template <typename BASE_TYPE>
Factory<BASE_TYPE>::Factory(OBJ_TYPE type)
{
  Registry().insert(std::make_pair(type,this));
}

template <typename BASE_TYPE>
typename Factory<BASE_TYPE>::FactoryRegistry& Factory<BASE_TYPE>::Registry()
{
  static Factory<BASE_TYPE>::FactoryRegistry  m_registry;
  return m_registry;
}

template <typename BASE_TYPE>
BASE_TYPE* Factory<BASE_TYPE>::Construct(OBJ_TYPE type)
{
  Factory<BASE_TYPE>::FactoryRegistry::iterator iter = Registry().find(type);
  if (iter != Registry().end())
  {
    Factory<BASE_TYPE>* pDerivedFactory = iter->second;
    return pDerivedFactory->Create();
  }
  else
    return NULL;
}

#endif

and here's an object and its factory...
// AnObject.h

#ifndef ANOBJECT_H
#define ANOBJECT_H

#include "Factory.h"
#include "BaseTypes.h"

class AnObject : public BaseObject
{
  int x,y;
public:
	AnObject():BaseObject(BaseObject::NextValidID()){
		SetType(TYPE_1);
	}
  void Initialise(int a, int b){x=a;y=b;}
};

class AnObjectFactory : public Factory<BaseObject>
{
public:
  AnObjectFactory():Factory<BaseObject>(TYPE_1){}
private:
  BaseObject* Create() const;
  static const AnObjectFactory register_this;
};

#endif

// AnObject.cpp
#include "AnObject.h"

#include <iostream>

const AnObjectFactory AnObjectFactory::register_this;

BaseObject* AnObjectFactory::Create() const
{
  AnObject* l_pObj = NULL;
  try
  {
    l_pObj = new AnObject;
  }
  catch(std::bad_alloc& ba)
  {
	  std::cout << ba.what() << std::endl;
  }
  return l_pObj;
}

and another object and factory
// AnotherObject.h
#ifndef ANOTHEROBJECT_H
#define ANOTHEROBJECT_H

#include "Factory.h"
#include "BaseTypes.h"

class AnotherObject : public BaseObject
{
  float x,y,z;
public:
	AnotherObject():BaseObject(BaseObject::NextValidID()){
		SetType(TYPE_2);
	}
  void Initialise(float a, float b, float c){x=a;y=b;z=c;}
};

class AnotherObjectFactory : public Factory<BaseObject>
{
public:
  AnotherObjectFactory():Factory<BaseObject>(TYPE_2){}
private:
  BaseObject* Create() const;
  static const AnotherObjectFactory register_this;
};

#endif

#include "AnotherObject.h"

#include <iostream>

const AnotherObjectFactory AnotherObjectFactory::register_this;

BaseObject* AnotherObjectFactory::Create() const
{
  AnotherObject* l_pObj = NULL;
  try
  {
    l_pObj = new AnotherObject;
  }
  catch(std::bad_alloc& ba)
  {
	  std::cout << ba.what() << std::endl;
  }
  return l_pObj;
}

Here's the base type from which these types are derived...
// BaseTypes.h

#ifndef BASETYPES_H
#define BASETYPES_H

#include "ObjectTypes.h"

typedef unsigned int ID_TYPE;

class BaseObject
{
	ID_TYPE  m_uID;
	OBJ_TYPE m_eType;

	static ID_TYPE m_uNextValidID;

	void SetID(ID_TYPE l_uID);

protected:
	void SetType(OBJ_TYPE type);
public:
	BaseObject(ID_TYPE l_uID);

	static ID_TYPE NextValidID();

	ID_TYPE ID(){ return m_uID; }
	OBJ_TYPE TYPE(){ return m_eType; }
};

#endif

#include "BaseTypes.h"


ID_TYPE BaseObject::m_uNextValidID = 0;

BaseObject::BaseObject(ID_TYPE l_uID)
{
	SetType(TYPE_DEFAULT);
	SetID(l_uID);
}

void BaseObject::SetType(OBJ_TYPE type)
{
	m_eType = type;
}


void BaseObject::SetID(ID_TYPE l_uID)
{
	m_uID = l_uID;
	m_uNextValidID++;
}


ID_TYPE BaseObject::NextValidID(void){
	return m_uNextValidID;
}

and finally an enumeration for identifying types...
// ObjectTypes.h

#ifndef OBJECTTYPES_H
#define OBJECTTYPES_H

enum OBJ_TYPE {TYPE_DEFAULT=0,TYPE_1, TYPE_2};

#endif

So, here is some sample usage...
// main.cpp

#include <iostream>

#include "Factory.h"
#include "AnObject.h"
#include "AnotherObject.h"


int main(void)
{
	BaseObject* pObj1 = Factory<BaseObject>::Construct(TYPE_1);
	BaseObject* pObj2 = Factory<BaseObject>::Construct(TYPE_2);

	AnObject* pAnObj1 = reinterpret_cast<AnObject*>(pObj1);
	pAnObj1->Initialise(1,2);

	std::cout << pObj1->TYPE() << ": " << pObj1->ID() << std::endl;
	std::cout << pObj2->TYPE() << ": " << pObj2->ID() << std::endl;

	return 0;
}

Now, the issue I have with this framework is having to use reinterpret_cast<>() to gain access to the Initialise member function of each type. Obviously, if the function signature of the initialisation routine of each derived class was the same, I could provide an abstract interface through the base class... but given that derived classes are expected to have different initialisation requirements and different initialisation lists, how can I get around this? I don't want to have to know the class type beyong the enumeration list... and I'd like to avoid using the 'ellipsis' construct. Any ideas about how I could change this framework to solve this problem? Are there standard solutions out (I've looked but couldn't find any). Thanks, Timkin

Share this post


Link to post
Share on other sites
Advertisement
I've read that article previously and I purposefully wanted to avoid the need to call a 'register' method to register a contructor in the factory. Hence the use of the derived factory technique for self registering during program startup... I was hoping I could handle the initialisation without compiler macros... but perhaps I'll need to...

If anyone has any alternative suggestions, I'm all ears.

Thanks,

Timkin

Share this post


Link to post
Share on other sites
I'm not sure how general you need the solution to be, but why not create a class containing the arguments needed for any derived object's initialization? That would allow you to provide an abstract interface through the base class, since you would just be passing an object of common type across all of your derived classes. You might have to get tricky with the class if you want it to be general and perform well, but it's all I can think of.

Share this post


Link to post
Share on other sites
An easy solution is to have a Data base class that gets passed into the Initialize function. The class could contain an identifier for its type. Then add the additional params in the derived class.

-= Dave

Share this post


Link to post
Share on other sites
Quote:
Original post by Sneftel
What are the advantages you see of using a factory here at all?


The source code I posted is ripped out of my game app and rebadged for clarity (I hope) so it's missing lots and the objects are greatly simplified (so it might not give a clear indication of my intent or usage)...

...but why am I looking at a factory pattern? Several reasons:

1) It's to be coupled with my warehouse (a set of repositories for each type in the game, accessed through a common interface), so since the warehouse hides the type behind a handle, it makes sense to do the same for object creation (at least from my perspective... ;) )

2) Because of the nature of the game, objects can be created by other objects and are destroyed through interaction with certain objects. It makes sense for object creation, storage and deletion to be managed.

3) The code is to be used and modified by someone else who knows almost no C++, so hiding some of the complexities of object management behind a simple class interface will make it easier for them to use this functionality without having to fully understand how it is achieved. My students will also be using the app and while at this stage they're not modifying this part of it, they may need to in the future... most of them don't know C++ before taking the course for which this game is used, so they're in the same boat.

If there is a better approach, I'm all ears. 8)

Cheers,

Timkin

Share this post


Link to post
Share on other sites
Quote:
Original post by Timkin
1) It's to be coupled with my warehouse (a set of repositories for each type in the game, accessed through a common interface), so since the warehouse hides the type behind a handle, it makes sense to do the same for object creation (at least from my perspective... ;) )
That seems reasonable.

Quote:
2) Because of the nature of the game, objects can be created by other objects and are destroyed through interaction with certain objects. It makes sense for object creation, storage and deletion to be managed.
Smart pointers (ideally, hidden behind a per-class static Create method, with constructors private) are a good way to handle this.

Okeydoke, from point 1 it seems a good idea to have a factory class. But that doesn't mean that the factory should only have one creation method. The fact that different objects will have different creation lists suggests to me that the user functions which call the factory have a pretty good idea of the sort of thing they're creating. So I'd suggest a factory with a different creation method for each type. If several types have the same initialization interface and are logically related it might be reasonable to group those particular ones behind the same creation method; mixing the two approaches is often the best solution.

Share this post


Link to post
Share on other sites
Quote:
Original post by Sneftel
But that doesn't mean that the factory should only have one creation method. The fact that different objects will have different creation lists suggests to me that the user functions which call the factory have a pretty good idea of the sort of thing they're creating. So I'd suggest a factory with a different creation method for each type. If several types have the same initialization interface and are logically related it might be reasonable to group those particular ones behind the same creation method; mixing the two approaches is often the best solution.


I'd advocate one interface for creation and an abstraction for the arguments.

1) A factory is a lower level library then the type of objects it creates. That is, objects depend on the factory for creation and the factory should have no knowledge of the objects its creating. If it did it would create a circular dependency.

2) Going with a creation method per object type will prevent any data driven solution in the future. It will always involve adding code to the factory to create new object types, this prevents abstraction for other scripting languages or tools to use.

3) Also, a smaller note but important in larger projects, changing the header file requires recompiling all .cpp files that use that header. On our current project we 100's of object types that use factories and having to change the interface every time we add a new parameter for an object type would hurt productivity.

With our solution we have a clear abstraction from objects and factories. The factory worries about memory management such as memory space to allocate from, reference counting, allocation stratigies(pools etc.). It constructs the object correctly, with arguments, and destroys it correctly all in a data driven manner that allows designers to construct new types of objects with no programmer intervention

-= Dave

Share this post


Link to post
Share on other sites
Quote:
Original post by Sneftel
So I'd suggest a factory with a different creation method for each type. If several types have the same initialization interface and are logically related it might be reasonable to group those particular ones behind the same creation method; mixing the two approaches is often the best solution.


Wouldn't that be functionally equivalent to instantiating factories further down the object hierarchy (so each factory becomes specialised on a derived class subtree)? For example, if I had

BaseObject
^ ^
| |
-------- --------
| |
Dynamic Static
Object Object

and presuming all DynamicObjects have the same initialisation interface and all StaticObjects had the same initialisation interface, then I could instantiate Factory<DynamicObject> and Factory<StaticObject>.

While this is workable (and I'd indeed considered this), it still places a requirement on knowing the different types that are in existence in terms of the actual class hierarchy, rather than an abstract token, such as a handle. Furthermore, there is a distinct risk of having to instantiate a Factory on a single derived class because its interface is unique.

One requirement I have for this is extensibility of the code and app. Presently we're talking about the creation of a set of objects that I know about. However, these objects are created by joining certain sorts of attribute objects together in a plug-and-play context. I'd like to be able to provide extensibility so that later new objects can be defined by new combinations of attributes and that the interface to this should be rather script-like.

Perhaps I'm approaching this in the wrong way. My intuition and experience tells me that if you need to hack the tool to make it work on a given problem, then either the problem is poorly formed or the tool is wrong for the job. Perhaps, rather than looking at alternative tools, I should look at alternative ways to describe the problem?

Dave, you wrote:
Quote:

I'd advocate one interface for creation and an abstraction for the arguments.

If you can spare a few minutes, could you elaborate on this please? Perhaps with a code snippet or some rough outlining?

Thanks for the help guys... 'tis appreciated.

Cheers,

Timkin

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!