When writing code, there are often multiple ways to achieve the same functionality. At first glance, it might seem logical to pick the easiest or quickest implementation. But great software development isn’t just about solving the problem at hand — it’s about anticipating the future.
Ask yourself:
Will my code be easy to extend when new requirements arise?
Is my code maintainable and self-explanatory for others (or even my future self)?
Does my code ensure optimal application performance?
This is where design principles and patterns come into play. They provide a roadmap for writing scalable, maintainable, and efficient code. SOLID principles underpin these best practices, while design patterns provide proven structures for solving everyday problems.
In this article, we’ll explore the Strategy pattern, a design pattern rooted in SOLID principles, and see how it helps us write clean, flexible, and future-proof code.
Now it is time to dive into the Strategy pattern. Let’s understand it with the following scenario.
Assume you are building a Military/war video game with different characters such as Soldiers, Knights, Archers, Giants, and builders. Each character is displayed differently in our game environment, and we use the Display() method to display each character. All the above characters can fight except the Builders. Builders can only build castles, monuments, and other buildings.
Soldiers and knights fight with swords,
Archers fight with their Archery.
Giants fight with their Hands.
Builders cannot fight.
Characters can fight according to the Fight() method.
How would you implement this?
You will create a super class as a Character and create each of these other characters as sub classes of it. Then you would create an abstract method as Display() . so you can implement the function in each character class. To handle each character fighting you will create a Fight() method in super-class. Only for Builder you will override the Fight() method to Do Nothing .
All these characters can move and they move by walking. So you will have to create a function as Move() in the super-class and return ‘Move by Foot’.
public class Character {
public abstract void Dispay();
public void Move(){
Console.WriteLine("Move by Foot");
}
public void Fight() {
Console.WriteLine("Fight by Sword");
}
}
public class Soldier extends Character{
public void Display() {
Console.WriteLine ("Display Soldier");
}
}
public class Builder extends Character {
public void Display() {
Console.WriteLine ("Display Builder");
}
// override Fight method
public override void Fight(){
Console.WriteLine("Cannot Fight");
}
// other subclasses goes here
}Do You think the above implementation is a good one?
What if we want to add a new character as a King, where he also cannot fight? Then we have to override the king’s Fight() method to do nothing. If we add more characters such as Blacksmiths, Healers, etc. who cannot participate in combat and fight, then we will have to override the Fight() method on multiple occasions and repeat same code. This leads to code duplication and violates the DRY (Don’t Repeat Yourself) principle.
Furthermore, consider a new character like a cavalry who can fight but moves by riding a Horse. This scenario would require overriding the Move() method for the cavalry class, introducing potential complexities in the inheritance hierarchy.
These challenges highlight the limitations of the current approach. The need to repeatedly override methods to handle specific cases and the potential for complex inheritance structures can significantly reduce code maintainability and increase the risk of introducing errors. Therefore we need find another method to handle these complex relationships.
Solution for inheritance is not more inheritance.
Introduce Strategy pattern
In Strategy pattern, we define strategies (or behaviors) and try to handle the scenario using composition rather than inheritance.
There is a design principle that says Favor composition over inheritanceI defined an interface as IFightBehavior and then created concrete sub-classes for each fighting method as FightWithHands, FightWithSwords, and FightWithArchery.
Next, I created another interface to handle movement behaviors as IMoveBehaviors, and defined sub-classes as MoveByFoot, and MoveByHorse. Concrete characters like Soldier, Knight, Builder and cavalry get their Fight and move behaviors not by inheritance, but by composing with right object of concrete implementation of behavior classes. This approach promotes flexibility and allows characters to dynamically change their behaviors at runtime.
Refer following UML Diagram.

public interface IFightBehavior{
public string Fight();
}
public class FightWithHands : IFightBehavior{
public string Fight() {
return "Fight by Hands";
}
}
public class FightWithSwords : IFightBehavior{
public string Fight() {
return "Fight by Swords";
}
}
public class FightWithArchery : IFightBehavior{
public string Fight() {
return "Fight by Archery";
}
// we can add more fight behaviors
}public interface IMoveBehavior{
public string Move();
}
public class MoveByFoot: IMoveBehavior{
public string Move() {
return "Move by Foot";
}
}
public class MoveByHorse: IMoveBehavior{
public string Move() {
return "Move by Horse";
}
// we can add more Move behaviors
}Next we will see how to implement this in our character classes.
public abstract class Character{
IFightBehavior fightBehavior;
IMoveBehavior moveBehavior;
public void Move();
public void Fight();
}
public class Soldier : Character {
Soldier(IFightBehavior fightBehavior, IMoveBehavior moveBehavior){
this.fightBehavior = fightBehavior;
this.moveBehavior= moveBehavior;
}
public void Fight(){
Console.WriteLine(this.fightBehavior.Fight());
}
public void Move(){
Console.WriteLine(this.MoveBehavior.Move());
}
// other methods goes here
}You can create objects in your main class as below.
public class Program{
public static void Main(){
FightWithHands fightWithHands = new FightWithHands();
MoveByFoot moveByFoot = new MoveByFoot();
Soldier soldier = new Soldier(fightWithHands,moveByFoot);
soldier.Fight();
soldier.Move();
// other code
}
}The official Definition
Strategy Pattern defines a family of algorithms, encapsulates each one
and make them interchangable, strategy lets the algorithm vary,
independently from the client that uses it We define the family of algorithms under each behavior interface and encapsulate the algorithms in our concrete behavior classes of the interface. We can use any concrete Move and Fight behaviors for each character. We can change the behaviors of each character by changing the object that the character calls without having zero effect on our application functionality.
Now, let’s see how Strategy Design Pattern adheres to SOLID Principles. Take each of, the five SOLID principles from the bag to see how the Strategy pattern follows it.
| SOLID Principle | How Strategy pattern implement it |
|---|---|
| Single Responsibility Principle | Characters, Fight and Move classes have their own responsibility. They change only when their responsibilities changes. |
| Open-Closed Principle | You can add new Fight and Move behaviors without changing existing concrete implementations, only need to implement the interface. (same with Characters) |
| Liskov substitution Principle | You can substitute concrete Fight and Move behaviors with any other concrete Fight and Move behaviors in our subclasses, without affecting the functionality of program. (Change knight MoveBehavior to MoveByHorse) |
| Interface segregation Principle | We have created many small interfaces instead of one large interface, if we find out more common scenarios in behaviors and character interfaces, we can create interfaces for them. |
| Dependency Inversion principle | High level implementations like Character interface are associated with high level interfaces like IFightBehavior and IMoveBehavior, While concrete implementations of each of them are associated with each other. |
I hope this article provided a clear understanding of the Strategy pattern. It’s considered a fundamental and relatively simple design pattern for programmers to grasp and implement effectively. I plan to delve deeper into the world of design patterns with future articles, exploring a wider range of these valuable concepts. I believe they will be incredibly beneficial for your software development journey.
Join the conversation! Your thoughts help the community grow.