Object
Allow the flexible creation of new “classes” by creating a single class, each instance of which represents a different type of object.
Let’s say we want to create monsters for our RPG. We’ll start out with an interface that defines its health and attacks.
class Monster {
public:
virtual ~Monster() {}
virtual const char* getAttack() = 0;
protected:
(int startingHealth) : health_(startingHealth) {}
Monsterprivate:
int health_;
};
And some implementations:
class Dragon : public Monster {
public:
() : Monster(230) {}
Dragon
virtual const char* getAttack() {
return "The dragon breathes fire!";
}
};
class Troll : public Monster {
public:
() : Monster(48) {}
Troll
virtual const char* getAttack() {
return "The troll clubs you!";
}
};
Eventually, when we want to have hundreds of different monsters, compile times become extremely slow and our designers and devs are frustrated. We take an email, create the class the designer wants, and recompile the code. We become data monkeys. We want to make this more available at runtime.
We normally think in an OOP fashion, with polymorphism. Thus, our hierarchy looks like this:
Dragon
Monster <- |
Troll
When this branches out to hundreds of classes, this leads to a large class hierarchy.
Instead, we can architect our code so that each monster has a breed.
Monster <- Breed
There’s no inheritance at all. The Breed
class that we
create contains the information that’s shared between all monsters. The
breed is essentially the type of the monster.
Define a type object class and a typed object class. Each type object instance represents a different logical type. Each typed object stores a reference to the type object that describes its type.
Instead of using vtables set up by C++, we’ll have to manually track the vtables ourselves.
With subclassing, we can override a method and do whatever you want to. With the type object pattern, we replace an overridden method with a member variable. This makes it easy to use type-specific data, but hard to define type-specific behavior.
Here’s the breed class:
class Breed {
public:
(int health, const char* attack)
Breed: health_(health),
attack_(attack)
{}
int getHealth() { return health_; }
const char* getAttack() { return attack_; }
private:
int health_; // Starting health.
const char* attack_;
};
Here’s the monster class:
class Monster {
public:
(Breed& breed)
Monster: health_(breed.getHealth()),
breed_(breed)
{}
const char* getAttack() {
return breed_.getAttack();
}
private:
int health_; // Current health.
& breed_;
Breed};
Each monster takes a breed in its constructor and holds a reference to it.
We have to construct a monster directly and pass in its breed. This is a little backwards:
Let’s make our constructor do this for us.
class Breed
{
public:
* newMonster() { return new Monster(*this); }
Monster
// Previous Breed code...
};
And the Monster class:
class Monster {
friend class Breed;
public:
const char* getAttack() { return breed_.getAttack(); }
private:
(Breed& breed)
Monster: health_(breed.getHealth()),
breed_(breed)
{}
int health_; // Current health.
& breed_;
Breed};
By making the monster’s constructor private, it can only be called by friend classes (of which our breed is). Thus, our breed can call the function directly in its constructor.