Strategy design pattern is the most easiest pattern out of all the design patterns and certainly the most useful one. There are high chances that you have written this type of code in your application but you wasn't aware if this thing have a name. But identifying certain types of code solutions with their name helps us to discuss those solutions with your team mates. It provides a common language to discuss these solutions.
If you have identified some area in your code which can be simplified using Strategy design pattern and you want to ask your team mate to do so, It's easy to ask them to implement Strategy design pattern there instead of explaining them (Well you should create an Interface for this and than create two classes which implements this interface and than select the implementations at run time). Well you can guess which one was easier to convey :).
I will not be using any of the complex UML diagrams explained by Gangs of Four to confuse you, Instead let's just get to the point straight:
Let's assume you have two Invoice Services (SMS Invoice, Email Invoice) to implement in your project, the service will provide two methods (GenerateInvoice, SendInvoice), and you have two implementations of InvoiceService already and there can be more Invoice services added to in future as well. So in that case we will implement the Strategy Design Pattern to reduce the complexity of the code. Strategy pattern highly relies on Inheritance.
Let's discuss the Invoice Services without Strategy and with Strategy.
Without Strategy Pattern:
InvoiceService.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace WithoutStrategyPattern.Services { public class InvoiceService { public string InvoiceText { get; set; } public void GenerateInvoice(string fullName, int amount) { InvoiceText = $"{fullName} have to pay a total amount of {amount}"; } public void SendInvoice(int sendInvoice) { if (sendInvoice == (int)sendInvoiceVia.sendInvoiceViaSMS) { Console.WriteLine($"{InvoiceText} sent via SMS"); } else if (sendInvoice == (int)sendInvoiceVia.sendInvoiceViaEmail) { Console.WriteLine($"{InvoiceText} sent via Email"); } } } public enum sendInvoiceVia { sendInvoiceViaSMS = 1, sendInvoiceViaEmail = 2 } }
Github! You can find code for Strategy Pattern on Github : https://github.com/ng-7015405148/DesignPatternsCSharp
As you can see in InvoiceService.cs, Both SMS Invoice & Email Invoice are handled in single class. The type of invoice generated will be decided based on paramter provided to SendInvoice method. So code for implementing both the Services will reside in SendInvoice class, which makes the code hard to manage for future use.
Below is the code for calling method for this service:
Program.cs
using System; using WithoutStrategyPattern.Services; namespace WithoutStrategyPattern { public class Program { public static void Main(string[] args) { Console.WriteLine("How you would like to get your invoice 1-2"); var userInput = Console.ReadLine(); int userInputInt; var userInputBool = int.TryParse(userInput, out userInputInt); if (!userInputBool) { throw new Exception("Please select the right Send Invoice method"); } InvoiceService invoiceService = new InvoiceService(); invoiceService.GenerateInvoice("Naveen Goyal", 2000); if (userInputBool) { invoiceService.SendInvoice(userInputInt); } Console.ReadLine(); } } }
Now this implementation is okay if we have two or three implementations of Invoice, but as more Invoice types will be added in future, code for InvoiceService will grow gradually. Here we have only printed the Inovoice type using Console.WriteLine. But in real project scenario, it would have been code of 200 lines atleast. And adding more Invoice types to it will add more lines to it.
Another big issue that arises here besides code management (one can argue if we refactor the Invoice implementation code to new methods) is every time we add a new Invoice type to it, we will have to test the whole functionality again. Because all the Invoice Services here are tightly coupled, issue in one type of InvoiceService can cause problem for other type of invoice service as well.This code can be refactored to use Strategy Pattern.
Using Strategy Pattern:
Here we will have separate class files for all the Invoice types and the calling method (In our case main.cs) will decide which strategy based on user entry. Strategy Pattern can be coupled with Factory Design Pattern to refactor the InvoiceService selection code from the main.cs.
Here are the steps to implement Strategy Pattern:
- Step 1: We first extract interface (In our case IInvoice.cs) for the Service Implementation.
- Step 2: We than define separate implementation for all the service implementing the IInvoice interface.
- Step 3: Than we add the Strategy selection code to Main.cs, which can be refactored out of Main.cs using Factory design pattern.
Github! You can find code for Strategy Pattern on Github : https://github.com/ng-7015405148/DesignPatternsCSharp
Step 1 (Extract Interface):
IInvoice.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace WithStrategyPattern.Services.InvoiceStrategy { public interface IInvoice { string InvoiceText { get; set; } void GenerateInvoice(string fullName, int amount); void SendInvoice(); } }
Extracted Interface out of Invoice Services.
sendInvoiceViaEnum.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace WithStrategyPattern.Services.InvoiceStrategy { public enum sendInvoiceViaEnum { sendInvoiceViaSMS = 1, sendInvoiceViaEmail = 2 } }
Step2 (Define separate implementation for each Service):
EmailInvoice.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace WithStrategyPattern.Services.InvoiceStrategy { public class EmailInvoice : IInvoice { public string InvoiceText { get; set; } public void GenerateInvoice(string fullName, int amount) { InvoiceText = $"{fullName} have to pay a total amount of {amount}"; } public void SendInvoice() { Console.WriteLine($"{InvoiceText} sent via SMS"); } } }
SMSInvoice.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace WithStrategyPattern.Services.InvoiceStrategy { public class SMSInvoice : IInvoice { public string InvoiceText { get; set; } public void GenerateInvoice(string fullName, int amount) { InvoiceText = $"{fullName} have to pay a total amount of {amount}"; } public void SendInvoice() { Console.WriteLine($"{InvoiceText} sent via Email"); } } }
Defined Separate implementation for each Invoice Service (Email Invoice Service, SMS Invoice Service)
Step 3 (Implement Strategy selection code)
Program.cs
using System; using WithStrategyPattern.Services.InvoiceStrategy; namespace WithStrategyPattern { class Program { static void Main(string[] args) { Console.WriteLine("How you would like to get your invoice 1-2"); var userInput = Console.ReadLine(); int userInputInt; var userInputBool = int.TryParse(userInput, out userInputInt); if (!userInputBool) { throw new Exception("Please select the right Send Invoice method"); } IInvoice invoiceService; if (userInputInt == (int)sendInvoiceViaEnum.sendInvoiceViaSMS) { invoiceService = new SMSInvoice(); invoiceService.GenerateInvoice("Naveen Goyal", 1000); invoiceService.SendInvoice(); } else if (userInputInt == (int)sendInvoiceViaEnum.sendInvoiceViaEmail) { invoiceService = new EmailInvoice(); invoiceService.GenerateInvoice("Naveen Goyal", 1000); invoiceService.SendInvoice(); } Console.ReadLine(); } } }
Here we have implemented the Strategy selection code within Program.cs. But as already explained the Strategy selection code can be refactored using Factory Design Pattern (Coming Soon). You can read about Factory Design Pattern in my list of Design Patterns.
Symptoms, when to use Strategy Design Pattern
- When we have several algorithm Implementations of same behavior with little variations.
- There are conditional statements which select those algorithm implementation (With similar behavior) at runtime.
Advantages of Strategy Design Pattern
- Easy to implement new algorithm implementation of same kind, as there is no coupling b/w existing implementations with new implementation.
- Separation of concerns, now we have different code blocks for different implementations.
- Less conditional statements complexity, this can be reduced further or refactored using Factory Design Pattern.
Hope you have got an essence of Strategy Design Pattern and when to use it.
Thanks for reading, Have a nice Day!
- Get link
- X
- Other Apps
- Get link
- X
- Other Apps
Comments
Post a Comment