The Strategy pattern explained

The Strategy pattern is about encapsulating a set of behaviors and having the client control the behavior through an interface, instead of hardcoding the behavior into the client function itself. What this means is that we want the game object to be completely independent of the behavior it uses. Imagine that we want to give each enemy a different attack and flight AI. We could use the Strategy pattern instead of creating an inheritance tree:

class Enemy: public Unit 
{
public:
Enemy(void);
virtual ~Enemy(void);
virtual void Update(float dt);
virtual void CollisionReaction(Object* pCollidedWith);
protected:
unsigned char m_color[4];
FlightAI* m_flight;
AttackAI* m_attack;
};

In this case, our client is the Enemy class and the interfaces that the client controls are the AttackAI and FlightAI. This is a much better solution than inheriting from the Enemy because we are only encapsulating what varies: the behavior. This pattern allows us to create as many FlightAI derived classes as we need and to reuse them to create different kinds of game object types, without needing to expand our inheritance tree. Since we can mix different strategy combinations, we can get a large number of different overall behaviors.

We are going to share the same strategies for both units and structures, so we should actually remove our inheritance tree altogether and just use the Object as our client. This way, the Object class becomes a collection of strategies, and our design is simpler. Plus, we are following some great programming principles:

  • Programing to an interface means that our client depends on behavior in an abstract class instead putting behavior in the client itself.
  • Our interfaces are opened for extension so we can easily add as many behaviors as we need. The interface is simple so it won't need to be changed, which might break code.
  • Our inheritance trees are shallow so we don't need to worry about the Diamond of Death.
Figure 3.6 - Example of our Object using the Strategy pattern

The Strategy pattern allows our game object to be very flexible without the need for an inheritance tree. With these six small classes shown in the preceding diagram, we can have a total of nine different game object behaviors. If we add a new FlightAI, we have 12 possible game object behaviors. Creating brand new strategies allows for an amazing amount of mixed behaviors. However, if we only extend just the two strategies, we don't need to modify the Object at all. This works for the Player as well, if we make an AttackAI and FlightAI that have access to input.

Staying with only two strategies is unlikely, which means that whenever we add a new strategy, we will need to change the Object by adding a new member and modifying the Update function. This means that while the pattern is flexible enough to let us change strategies at runtime, we can't add behaviors dynamically. If we need to add acid damage as a debuff in our game, we would need a Damage base class, and to give a Damage base class pointer to the object:

class Object 
{
public:
//Same as before...
protected:
//Other Object Strategies
//...
Damage* m_damage.
};

This doesn't seem like a great solution because most damage will be instantaneous and, most of the time, the player isn't even taking damage. That means this will be either null or an empty strategy class, such as using a NoDamage derived class, that will be updated every frame but will do nothing. This is also no way to stack corrosive effects or to have two types of damage affecting the Player, such as corrosive damage and ice damage, which might cause the Player to move slower for 10 seconds. We really need a way to dynamically add and remove these abilities. Luckily, there is a pattern for that.

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

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