The first design pattern I’d like to talk about is Strategy Pattern. The main purpose of the Strategy pattern is to decouple concrete code into separate classes, which promotes reusability. As your project grows in size, having reusable modules is a necessity. This pattern also allows you to easily add new modules into a C# ASP .NET software design with limited impact on the rest of the system.
Let’s assume one situation that you are trying to print out a greeting message on screen in different languages. Here is the non-pattern way how your code may look like:
class HelloWorldClientNoPattern
{
public void SayHello(string strName, string strLanguage)
{
switch (strLanguage)
{
case "english":
Console.WriteLine("Hello World " + strName);
break;
case "spanish":
Console.WriteLine("Hola mundo " + strName);
break;
case "german":
Console.WriteLine("Hallo Welt " + strName);
break;
}
}
}
class Program
{
static void Main(string[] args)
{
HelloWorldClientNoPattern client = new HelloWorldClientNoPattern();
client.SayHello("Can Lu", "english");
client.SayHello("Can Lu", "spanish");
client.SayHello("Can Lu", "german");
}
}
What is wrong with this approach? Well it is nothing wrong it works but there are at least couple of problems.
- The code above is not reusable. If we want to use the SayHello method somewhere else, I have to write the same code again.
- The code is not decoupled. If you want to add a new greeting language. For example if you want to print SayHello in Chinese, you have to modify the client class.
The strategy pattern is to solve this kind of problem. The most important part of the Strategy pattern is the main interface. This is what allows the polymorphism and allows us to pass the client any type of algorithm, as long as it uses the same interface. The interface will look like:
public interface IHelloWorldStrategy
{
void SayHello(string strName);
}
Then it’s time to create different classes to implement this interface. For example we could have:
class HelloWorldEnglish: IHelloWorldStrategy
{
public void SayHello(string strName)
{
Console.WriteLine("Hello world " + strName);
}
}
class HelloWorldChinese: IHelloWorldStrategy
{
public void SayHello(string strName)
{
Console.WriteLine("Ni Hao " + strName);
}
}
Now we can rewrite our client class. Instead of using Switch Case/If then else, we're using an Interface as the implementation of the strategy. This allows us a generic pointer to whichever algorithm we end up actually using.
class HelloWorldClient
{
IHelloWorldStrategy strategy;
public HelloWorldClient(IHelloWorldStrategy s)
{
this.strategy = s;
}
public void SayHello(string strName)
{
strategy.SayHello(strName);
}
}
Notice the constructor takes a variable of the interface type. This is powerful because, if we derive each algorithm class from the same interface, then we can pass the client algorithm and it will be able to handle any kind algorithms that we create; as long as they implement the same interface, can be utilized in this client.
Please find the full working code below:
using System;
namespace StrategyPatternDemo
{
public interface IHelloWorldStrategy
{
void SayHello(string strName);
}
class HelloWorldEnglish: IHelloWorldStrategy
{
public void SayHello(string strName)
{
Console.WriteLine("Hello world " + strName);
}
}
class HelloWorldSpanish : IHelloWorldStrategy
{
public void SayHello(string strName)
{
Console.WriteLine("Hola mundo " + strName);
}
}
class HelloWorldGerman : IHelloWorldStrategy
{
public void SayHello(string strName)
{
Console.WriteLine("Hallo Welt " + strName);
}
}
class HelloWorldClient
{
IHelloWorldStrategy strategy;
public HelloWorldClient(IHelloWorldStrategy s)
{
this.strategy = s;
}
public void SayHello(string strName)
{
strategy.SayHello(strName);
}
}
class Program
{
static void Main(string[] args)
{
HelloWorldClient myHelloWorld = new HelloWorldClient(new HelloWorldEnglish());
myHelloWorld.SayHello("Can Lu");
myHelloWorld = new HelloWorldClient(new HelloWorldSpanish());
myHelloWorld.SayHello("Can Lu");
myHelloWorld = new HelloWorldClient(new HelloWorldGerman());
myHelloWorld.SayHello("Can Lu");
}
}
}
The output will be like:
There are a number of advantages to structuring a family of algorithms in this manner, but the most important is that doing so decouples the client from the implementation details of any particular algorithm. This promotes extensibility in that additional algorithms can be developed and plugged in seamlessly, as long as they follow the base interface specification, thereby allowing algorithms to vary dynamically. Moreover, the Strategy pattern eliminates conditional statements that would otherwise litter client code. Whenever we have switch case statement in code, we probably need to think twice, should we use Strategy Pattern to replace it.
Your post is important just because of the last paragraph, which provide the usage of the pattern. In my experience, most of the patterns explanations lack just that - usage(when to apply them in a real world). You've shown exactly that. Thanks! It realy helped me.
ReplyDelete