Parametized class creation with assert of parameter presence. Use factory?
I spend about 2 hours reading a lot of factory related topics and I still am not sure if this would be the right approach.
Here is the thing: I have an Enemy class 开发者_运维问答that has contains member variables like name_ and health_. I want to create instances of it with different values of these member variables and my first approach was to get the parameters from a static array with the properties:
Enemy::Enemy(int type) : name_(properties[type].name),
health_(properties[type].health)
{
...
}
The problem here is that I can't check if the array properties is already filled. I would have to check it from outside by calling a static Enemy::initArray() function but this would defeat the encapsulation of the Enemy class.
Is this the time to create a factory that would initialize the properties array in it's constructor? And then create Enemies with:
Enemy* EnemyFactory::create(type);
I read that usually factories are created when you have complex class hierarchies or the factory caller only needs to know the interface of the created classes. I only need the encapsulation of the array creation and verification. Is there a "lighter" solution?
EDIT: I'll try to be more clear:
1.) I think I know how to create a factory. The main question is whether there is an alternative!
2.) I don't want to set the properties inside the object but use initialization lists. If I do the former I can just check the array in the constuctor and don't need the factory at all.
There are essentially three options:
Everybody is free to specify the initial name and health of an
Enemy
, which accepts both as parameters in its (public) constructor.The
Enemy
class constructor maps an 'Enemy ID' to the appropriate values of the properties. The problems in this approach come when you need to verify the validity of the 'Enemy ID' and/or the presence of the mapping, while also using initialiser-lists for the properties. This is typically solved by adding a dummy member/base-class, which is initialised by calling a verification function.
This would typically look like this:
class Enemy
{
bool dummy_must_be_first;
public:
Enemy(int type) : dummy_must_be_first(validate(type), name(properties[type].name), health(properties[type].health) {}
private:
bool validate(int type)
{
// ensure properties is initialised and verify type is within range...
// on failure, throw an exception
return true;
}
string name;
int health;
};
- You use a factory function to perform the mapping between an 'Enemy ID' and the properties that are used to initialise the
Enemy
object.
There is no need to have a separate factory class. For this, a factory-method would be sufficient:
class Enemy
{
private:
Enemy(string name_, int health_) : name(name_), health(health_) {}
string name;
int health;
public:
static auto_ptr<Enemy> createEnemy(int type)
{
// ensure properties is initialised and verify type is within range...
return auto_ptr<Enemy>(new Enemy(properties[type].name, properties[type].health));
}
};
I see two possibilities.
name_(default_name_value), health_(default_health_value)
and then do the actual assigning in the constructor body
or
use the construction method
Your factory would have a reference to properties. Enemy should take name and health as its parameters in its constructor.
Then something like:
Enemy* EnemyFactory::create(int type) const
{
return new Enemy( properties[type].name, properties[type].health );
}
If these properties are not found, you need to handle this case, either by throwing an appropriate error message or allowing a default value.
Suggestion 1
How about introducing an EnemyCreator?
class EnemyCreator
{
protected:
Enemy* createImpl() const = 0;
public:
// ctors
Enemy* create() const
{
return(createImpl());
}; // eo create
}; // eo class EnemyCreator
// a concrete creator
class EnemyType1Creator : public EnemyCreator
{
protected:
Enemy* createImpl() const
{
Properties p;
// ...
// fill in properties here
//
return new Enemy(p); // Enemy will populate
};
}; // eo class EnemyType1Creator
Then modify your EnemyFactor to manage a bunch of EnemyCreator objects.
Suggestion 2
You say that the property-set number and types are the same, they just vary from enemy to enemy. Another way would be to have a base-class that sets up enemy properties in a generic fashion (assumes _property
is a member of Properties
and accessible):
class EnemyProperties : public Properties
{
protected:
virtual void initialiseImpl() = 0;
public:
void initialise()
{
// set up the properties all enemies have
_property["healh"] = 5;
_property["skill"] = 0.5f;
_property["skillWithABanana"] = 1.0f;
// now let derived classes specialise
initialiseImpl();
};
}; // eo class EnemyProperties
Now we can make property sets for any number of types and only change what we want:
class BadBananaEnemy : public EnemyProperties
{
protected:
void initialiseImpl()
{
_property["skillWithABanana"] = 0.0f; // he's bad at this.
}; // eo initialiseImpl()
}; // eo class BadBananaEnemy
etceteras.
You can then put these in a factory that can dish these out by name:
class EnemyFactory
{
private:
typedef std::pair<std::string, EnemyProperties> EnemyPair;
typedef std::map<std::string, EnemyProperties> EnemyMap;
EnemyMap m_EnemyTypes;
public:
// register a type
void registerType(const std::string& _name, const EnemyProperties& _prop)
{
m_EnemyTypes.insert(EnemyPair(_name, _prop));
}; // eo registerType
// create an enemy
Enemy* createEnemy(const std::string& _type)
{
EnemyMap::iterator it(m_EnemyTypes.find(_type));
if(it != m_EnemyTypes.end())
return(NULL);
else
return(new Enemey(it->second));
}; // eo createEnemy
}; // eo class EnemyFactory
精彩评论