Strategy Design Pattern
Problem​
You: Hi Shubh, today, I'm building a game where we get to be the king of our kingdom. We'll have different types of soldiers, like Warriors, Wizards, Archers, etc. They will each have different attack strategies.
Shubh: Oh nice! You're creating a very interesting game. I hope it becomes popular.
You: Thanks! But here's the problem: at runtime (when the game is running), how will a character know their attack strategy? Should I write a bunch of if-else conditions (e.g., if a character is an archer, then shoot arrows; else if a character is a warrior, then punch, etc.)?
Shubh: That's definitely not a good idea! It might solve the problem now, but if there are hundreds of different kinds of characters, your code will become unnecessarily huge.
Shubh: Also, imagine if you have to write the same logic in another file. You would end up writing those if-else statements again.
You: Yeah, I've thought about that. Another way I can think of is to create an interface Character
and implement it with Warrior
, Wizard
, and Archer
, each having its own behavior for the attack()
method.
Shubh: This would violate the Interface Segregation principle of SOLID principles. In the future, if you introduce a fly()
method in Character
, then all implementations like Warrior
and Archer
would have to implement it, even if they don't need it. That's not good.
Shubh: Another problem is if you have four implementations: Warrior
, Wizard
, Archer
, and Commander
, and Warrior
and Commander
have the same attack strategy. Remember the DRY principle (Don't Repeat Yourself). We're violating it here by writing the same behavior in both Warrior
and Commander
.
You: Is there a better way to handle this?
Shubh: The Strategy Design Pattern is the way to solve this issue. It lets you inject the algorithm at runtime. For example, inject a punching algorithm for a warrior at runtime, inject a shooting arrows algorithm for an archer, and so on.
You: Wow! Can you explain this using a UML diagram?
Shubh: Sure.
UML​
Shubh: In the UML diagram, we have:
-
Strategy Interface: This defines a method
attack()
that various strategies will implement, such as melee, spell, and ranged attacks. -
Concrete Strategies: Classes like
MeleeAttack
,SpellAttack
, andRangedAttack
implement theAttackStrategy
interface, providing specific behaviors for each type of attack. -
Context (
Character
): TheCharacter
class has ahasA
relationship withAttackStrategy
, meaning it can hold a reference to anAttackStrategy
object. This allows it to change its behavior at runtime by using thesetAttackStrategy(AttackStrategy attackStrategy)
method. Whenever you callattack()
, the character will attack according to the currently set strategy.
Code Implementation​
AttackStrategy Interface​
public interface AttackStrategy {
void attack();
}
MeleeAttack Class​
class MeleeAttack implements AttackStrategy {
@Override
public void attack() {
System.out.println("Performing a melee attack!");
}
}
SpellAttack Class​
class SpellAttack implements AttackStrategy {
@Override
public void attack() {
System.out.println("Casting a spell!");
}
}
RangedAttack Class​
class RangedAttack implements AttackStrategy {
@Override
public void attack() {
System.out.println("Performing a ranged attack!");
}
}
Character Class​
class Character {
private AttackStrategy strategy;
public void setStrategy(AttackStrategy strategy) {
this.strategy = strategy;
}
public void attack() {
strategy.attack();
}
}
Main Class (Client Code)​
public class Main {
public static void main(String[] args) {
Character character = new Character();
// Warrior uses melee attack
character.setStrategy(new MeleeAttack());
character.attack();
// Mage uses spell attack
character.setStrategy(new SpellAttack());
character.attack();
// Archer uses ranged attack
character.setStrategy(new RangedAttack());
character.attack();
}
}
Key Benefits of the Strategy Design Pattern​
Before watching, make sure you give it a try
-
Flexibility: The Strategy Pattern allows you to change the behavior of a class by switching its strategy at runtime. This makes your code more flexible and adaptable to changes.
-
Single Responsibility Principle: The Strategy Pattern adheres to this principle by allowing you to encapsulate algorithms or behaviors in separate classes, making your code more modular.
-
Open/Closed Principle: The Strategy Pattern makes it easy to add new strategies without modifying existing code. You can introduce new behaviors by simply creating new strategy classes.
-
Avoids Conditional Statements: By using the Strategy Pattern, you can avoid complex if-else or switch-case statements, making your code cleaner and easier to understand.
-
Reusability: Strategies can be reused across different contexts. For example, the MeleeAttack strategy can be used by both Warrior and Commander, promoting code reuse and reducing redundancy.