The Strategy Design Pattern allows us to change the behaviour of a class at runtime. In Strategy pattern we create various algorithm classes implementing a common Strategy interface. The Context object has a reference to a Strategy object and we can change the behaviour of the Context object by changing it's strategy object reference.
Strategy Pattern evolves three actors
- Strategy : We define a common interface which every algorithm class must implement. Context interacts with various strategy implementation using this interface only. Context is not aware of that with which Strategy implementation he is interacting with.
- ConcreteStrategy Classes : Concrete implementation of Strategy interface. Each class implementing Strategy interface implements an algorithm.
- Context : It contains a reference to Strategy object. A context implements all common behaviours which doesn't vary and depends on Strategy object for performing any variable behaviour. When context object receives requests from the client to perform any variable behaviour it delegates them to the Strategy object.
, We are designing a new Abstract Data Type ArrayADT which is a wrapper over Array. We don't want to hard-code any specific sorting algorithm for sorting array elements instead we will let client to specify which sorting algorithm he wants to use. We will define an interface 'SortingAlgo' containg sort() mothod. ArrayADT will contain a reference to SortingAlgo object and we will provide a public method in ArrayADT to set SortingAlgo object. To sort the array, ArrayADT will delegate request to SortingAlgo object. Now, We can write multiple concrete classes implementing SortingAlgo interface like 'BubbleSort', 'QuickSort', 'MergeSort' etc.
Advantages of Strategy Design Pattern
- Algorithm Encapsulation :This approach encapsulates algorithms in distinct strategy classes, fostering a clear separation of concerns. Each strategy class concentrates on a particular algorithm, enhancing the modularity of the code.
- Open/Closed Principle : This approach aligns with the Open/Closed Principle, enabling the addition of new strategies without altering existing code. This promotes the maintainability and stability of the system.
- Easy Algorithm Switching : The context effortlessly shifts between different algorithms at runtime by modifying the current strategy. This flexibility permits dynamic adaptation to evolving requirements.
- Supports Code Reusability : Specific strategy classes can be reused across various contexts if they adhere to the same strategy interface. This reusability proves advantageous when similar algorithms are required in multiple sections of a system.
- Adaptability to Changing Requirements : The Strategy Design Pattern is adaptable to changing requirements. Introducing new approaches and modifying or extending existing ones can occur without disrupting the overall structure of the system.
- Minimize Code Duplication : This approach aids in minimizing code duplication by providing an organized way to structure and reuse algorithmic code. This is especially valuable when multiple components in a system demand similar algorithms.
- Improved Maintainability : Maintenance is streamlined as alterations to one algorithm do not impact other parts of the system. Changes or additions to strategies are localized, mitigating the risk of unintended side effects.
- Clear Separation of Concerns : The division of concerns between the context and strategies results in a lucid and modular design. The context concentrates on managing algorithms, while strategies focus on implementing specific algorithms.
When we should use Strategy Pattern
- When We want to change the behaviour of a class at runtime.
- When we want to decouple the Context class form its variable behaviour. We can add or modify a ConcreteStrategy without modifying Context class.
Implementation of Strategy Design Pattern
Here, we will use strategy pattern to design a Soldier class for a video game, in which a player can change the gun of the solder any time. First of all, we will define a Strategy interface called 'Gun'. Different types of guns must implement Gun interface.
Gun.javapublic interface Gun { public void fire(); }
We will define various weapon available for soldier. Each weapon is implemented as ConcreteStrategy class implementing Gun interface.
Snipper.javapublic class Snipper implements Gun { public void fire(){ System.out.println("This is Snipper Gun," + "Firing one bullet at a time"); } }
MachineGun.java
public class MachineGun implements Gun { public void fire(){ System.out.println("This is Machine Gun," + "Firing 100 bullets at a time"); } }
GrenadeLauncher.java
public class GrenadeLauncher implements Gun { public void fire(){ System.out.println("This is Grenade Launcher," + "Throwing one grenade"); } }
Now, we will define Soldier(Context) class having a reference to Gun object and a public method 'changeGun' for changing its Gun reference(Strategy object)
Soldier.javapublic class Soldier { private Gun gun; public Soldier(Gun gun){ this.gun = gun; } public void changeGun(Gun gun){ this.gun = gun; } public void fireAtEnemy(){ gun.fire(); } }
StrategyPatternExample class demonstrate the changing of Guns(Strategy) of soldier(Context) at runtime.
StrategyPatternExample.java/** * In this class we will dynamically change the Gun of a soldier as Soldier * deals with Gun interface not concrete implementation of Gun. */ public class StrategyPatternExample { public static void main(String args[]) { Gun snipper = new Snipper(); Gun machineGun = new MachineGun(); Gun grenadelauncher = new GrenadeLauncher(); // Create a soldier object with Snipper gun Soldier commando = new Soldier(snipper); commando.fireAtEnemy(); // Change the gun of commando to Machine Gun commando.changeGun(machineGun); commando.fireAtEnemy(); // Change the gum of commando to Grenade Launcher commando.changeGun(grenadelauncher); commando.fireAtEnemy(); } }
Output
This is Snipper Gun, Firing one bullet at a time This is Machine Gun, Firing 100 bullets at a time This is Grenade Launcher, Throwing one grenade
Guidelines for Implementing Strategy Design Pattern
- Clearly articulate the methods embodying the algorithm within the strategy interface. A well-defined interface simplifies the implementation of concrete strategy classes.
- Each concrete strategy class should encapsulate a distinct algorithmic variation. Avoid amalgamating multiple algorithms within a single strategy class to preserve clarity and modularity.
- Consider utilizing an abstract class for the strategy instead of an interface if there are shared behaviors among multiple strategy classes. This abstract class can supply default implementations for shared behaviors.
- Ponder incorporating a default strategy in the context class to handle situations where a specific strategy is not explicitly set. This default strategy helps forestall null references and ensures consistent behavior.
- Curtail the frequency of strategy switches during runtime. Frequent switching may introduce needless complexity and diminish the effectiveness of the pattern.
- Contemplate utilizing dependency injection to furnish strategies to the context. This approach permits greater flexibility in substituting various strategy implementations, simplifying system extension.
- Furnish explicit documentation for each strategy class, elucidating the expected behavior in each strategy. This documentation aids developers in comprehending how to implement new strategy classes.
- Determine whether state information should be managed in the context or within individual strategy classes. Considerations may involve the necessity for shared state or whether state should be exclusive to each strategy.
- Decide whether the initial strategy should be established during the context's construction or if it should be explicitly set by client code. Deliberate on the initialization strategy that best aligns with your application's requirements.
State Design Pattern |
Command Design Pattern |
Composite Design Pattern |
Singleton Design Pattern |
Builder Design Pattern |
List of Design Patterns |