In C++ template design, "traits" and "policies" pattern are common.
Consider this simple inheritance based class design
{
public:
int hp;
int stamina;
Player(int hp, int stamina) :hp(hp), stamina(stamina){}
Player() :Player(20, 10){}
bool isAlive() const{ return hp > 0; }
bool isExhausted() const{ return stamina <= 0; }
virtual void attack(Player&) = 0;
};
class Swordsman : public Player
{
public:
Swordsman() : Player(100, 50){}
void Attack(Player& enemy) override{
this->stamina -= 10;
enemy.hp -= 30;
}
};
We can achieve the similar result with class template, using traits and policies.
For this example, we refactor role information into enum class.
enum class Roles { Swordsman, Bandit, Knight };
Traits class is a class which holds information. The information can be in any forms, such as const values, typedefs etc.
If it is a class template, then the information provided can be specific to its template arguments and it uses template specialization to reach its purpose.
Back to our example, we can use Traits to define the maximum hp and stamina.
class PlayerTraits
{
public:
static const int hp = 20;
static const int stamina = 10;
};
template<>
class PlayerTraits<Roles::Swordsman>
{
public:
static const int hp = 100;
static const int stamina = 50;
};
Policy class is a class which represents algorithm / behavior.
You can think it as GoF's strategy pattern implemented via template.
In this case, we refactor attack function into new policy class.
{
public:
template<typename T1, typename T2>
static void Attack(T1& attacker, T2& victim)
{
attacker.stamina -= 10;
victim.hp -= 30;
}
};
We also need to alter Player class itself to template based with traits and policy arguments.
class Player{
public:
int hp;
int stamina;
Player()
{
hp = Traits::hp;
stamina = Traits::stamina;
}
bool isAlive() const{ return hp > 0; }
bool isExhausted() const{ return stamina <= 0;}
template<typename T2>
void attack(T2& enemy){
Policy::Attack(*this, enemy);
}
};