Strategy Pattern - Composition over Inheritance

The strategy pattern is a subtle pattern. It’s all about minimizing the duplication of code and decoupling classes. As an added bonus, the strategy pattern can also allow behaviors or algorithms to swapped at runtime without any messy switch statements or long chains of if statements. In the end it doesn’t have a super flashy or exciting outcome. It’s just good coding practice.

With some aspects of the pattern it’s easy to think, “Yeah, but this other way works…” But! The pattern is solid and avoids lots of little issues that can pop up later down the road when your project gets bigger.

Before we jump into the pattern lets first look at the problem it solves.

Inheritance Is Not Always Awesome

WeaponBase_Unpattern.png

Let’s imagine you’re making a game based around weapons, or at least has a lot of them in your game. It would seem reasonable to create a “Weapon Base” class that other weapons can inherit from. This base class can contain basic stats and functions that can be used or overridden by sub-classes.

For example, we may have a DoDamage function that gets called every time a weapon is used. The function might simply reduce the health of the player’s target.

This is all reasonable.

Going a step further, let's imagine that we want to create 3 fire-based weapons that will all inherit from the WeaponBase and on top of reducing the targets health will also do some actions specifically for fire damage.

FireDagger_Unpattern.png
FireSword_Unpattern.png
FireAxe_Unpattern.png

I now have 3 new classes that all have duplicated code. The DoDamage function has the same code from the base class, plus fire damage specific parts.  Updating the fire behavior means opening and changing the code in all the every fire weapon class in your project. This isn’t horrible with 3 weapons, but imagine having 20 or 50 or 100 weapons. Yeah, that’s not going to work.

I could also call base.DoDamage, but then all my weapons would be dependent on the base class DoDamage function, which is definitely NOT AWESOME.  If the base class function changes, all the inheriting classes change too and that’s not good. That’s not a solid foundation to build on. That’s a way to break your game in a hurry. This coupling between classes is what we want to reduce!

Now you might now argue that you could create a “Fire Weapon” class that inherits from the weapon base class and that all fire weapons inherit from… Which may work, but it is starting to get messy. Imagine now that you want to add ice or poision damage? You’d have to create Ice Weapon and Poison Weapon classes that those new weapons have to inherit from.

Okay, push comes to shove this might still be okay… Ugly, but okay, if the project stays small.

What if you now have a weapon that will do both fire and poison damage? Which class does it inherit from? Fire or Poison? Or do you make a combo class to inherit from? NO! Please don’t.

The strategy pattern can help solve these problems…

Strategy Pattern

The strategy pattern is all about encapsulating or wrapping up a behavior or algorithm in it’s own class. It’s also very closely related to the concept or belief that composition is better than inheritance! The exact details of how we do this are less important than the overall pattern so let’s start with a simple and common way to implement this pattern.

Interface.png

First, we create an interface called “IDoDamage” (you can argue all you want about using “I” to name an interface - I don’t care). This interface will have one function called “DoDamage.”

At this point, you might be thinking, “Okay, we’ll just implement the interface in all our weapons.” And that would be understandable, but it would be a mistake to do that as that would cause lots of duplicate code and not really buy us much in return from just good old inheritance.

WeaponBase_Pattern.png

Instead, we are going to create an instance variable of this interface in the Weapon_Base class. This class will also have a function that calls the DoDamage function on the IDoDamage variable.

Why? Good question. This is the crux of the whole pattern.

FireDamage.png

We can create classes that implement the IDoDamage interface. Each of these classes will have a different damage behavior. This will encapsulate the damage behavior AND make it so that we can change behavior at runtime by a simple assignment - no ugly switch statement or crazy chain of if statements needed.

For example, we can create a “FireDamage” class. This can do all the basic damage bits and most importantly it can then do any fire specific bits - maybe there are events that play sound effects or trigger specific lighting effects.

Then!

We create a new class for each weapon that inherits from Weapon_Base. Rather than hiding variables or overriding functions we use a constructor to set basic variable values AND to set the damageType variable.

FireDagger_Pattern.png
FireSword_Pattern.png
FireAxe_Pattern.png

While we now have a poop ton of classes, which could be a criticism of the pattern, we have very little duplicated code, and if we need to change the fire damage behavior, it only needs to be changed in one place in our project.

There is a neatness, a tidiness, a cleanliness that just feels good with this implementation. All we are doing is using a constructor to set up the weapon. The entirety of the damage algorithm or behavior is fully encapsulated in another class. While we are still using inheritance, we have decoupled much of our code, and much of the messiness of inheritance isn’t present in our solution.

Adding More Behaviors

The strategy pattern also works if you want to create other types of damage, such as IceDamage. To implement this style of attack, we need to create new IceDamage and IceSword classes.

IceDamage_Pattern.png
IceSword_Pattern.png
GenericSword_Pattern.png

Going Abstract

You could go either further and create generic weapons that have their damage and damage type set by a constructor. This could allow generic classes for each weapon type with all the data PLUS the behaviors injected into it.

Changing Behaviors

And I think the real cherry on top is that with the strategy pattern is that it allows easy changing of behaviors at runtime. Sure, you could do that with some if or switch statements. But those tend to be ugly. They break. They’re generally a brittle approach to programming and we can do better.

ChangeBehaviors.png

We can add a function to Weapon_Base to allow the damageType variable to be set. This would have the effect of changing behaviors. Something the code on the right.

Yes, I realize I made the variables public, but I don’t like changing values in classes from outside the class without using a function. If this was my project, I’d probably use private variables or maybe a public getter. 

With this functionality, a click of a button or the invoking of an event can change the weapon's damage type and thus much of it’s behavior.

If that’s not useful. I’m not sure what is.

Combining Behaviors

MultipleTypes.png

What if you really want that fire poison sword? Maybe your game is based around combining behaviors or abilities? Then what?

Make a list of IDoDamage values. The code can then iterate through the list and called DoDamage on each item in the list?

I’ll be honest I haven’t tried this but it seems solid and pretty useful.

Other Thoughts

The choice to use an interface in the strategy pattern is not the only choice. You may want to use an abstract class instead so that you can define variables. Personally I like the cleanliness of the interface and then simply injecting any needed data.

I also thought to use scriptable objects. And while I think that would work, I think it’s stretching SOs to a place they don’t fit particularly well. Writing the classes and then creating assets seemed like too many steps and I was struggling to find a situation where that would truly be better. But maybe I’m wrong?

I also wrestled with making the base class a MonoBehaviour or not. For simplicity I kept it as a MonoBehaviour so I could easily attach it to a button (for the video). I think that choice really depends on the use, but my gut say most the time I’d want it to NOT be a MonoBehaviour.