Introduction
Hopefully you are familliar with the Strategy-Pattern as defined and explained in the original article by Volkan Paksoy:
Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
To his article I want to add the aspect, that in addition to implement an Interface there are two other approaches of implementing the Strategy-Pattern.
These approaches are: 1) overriding an abstract baseclass 2) using delegates with appropriate signature.
I simply show the code without detailed explainations, because the "working-part" of it is already introduced in the base-article.
I only wrapped it in different ways, using other c#-language-features than Interfaces.
Each approach: Interface / Base-Class / Delegate has its own advantages and disadvantages, so it might be useful, to know them all.
SP by Overriding a BaseClass
abstract class IpCheckBase {
public string GetIp() {
using (var client = new HttpClient()) {
return GetIpCore(client);
}
}
protected abstract string GetIpCore(HttpClient client);
}
class DynDnsIPCheckStrategy : IpCheckBase {
protected override string GetIpCore(HttpClient client) {
client.BaseAddress = new Uri("http://checkip.dyndns.org/");
HttpResponseMessage response = client.GetAsync("").Result;
return HelperMethods.ExtractIPAddress(response.Content.ReadAsStringAsync().Result);
}
}
class AwsIPCheckStrategy : IpCheckBase {
protected override string GetIpCore(HttpClient client) {
client.BaseAddress = new Uri("http://checkip.amazonaws.com/");
string result = client.GetStringAsync("").Result;
return result.TrimEnd('\n');
}
}
class CustomIpCheckStrategy : IpCheckBase {
protected override string GetIpCore(HttpClient client) {
client.BaseAddress = new Uri("http://check-ip.herokuapp.com/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = client.GetAsync("").Result;
string json = response.Content.ReadAsStringAsync().Result;
dynamic ip = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
string result = ip.ipAddress;
return result;
}
}
You see 4 classes: the first is abstract, and contains general logic, which each Derivate needs. The Derivates have no own public members - they only override the placeholder with specific strategy.
Note: Override-Strategy-Architecture also can be more simple as well as more complicated.
Edit: As marcus obrien points out in his comment, the base-class not necessarily requires to be abstract. Even a Class-Derivate with virtual-override(s) sometimes can be seen or used as an alternate Strategy to the base-strategy.
SP by Delegates
public readonly static Func<string> DynDnsIPCheckStrategy = () => {
using (var client = new HttpClient()) {
client.BaseAddress = new Uri("http://checkip.dyndns.org/");
HttpResponseMessage response = client.GetAsync("").Result;
return HelperMethods.ExtractIPAddress(response.Content.ReadAsStringAsync().Result);
}
};
public readonly static Func<string> CustomIpCheckStrategy = () => {
using (var client = new HttpClient()) {
client.BaseAddress = new Uri("http://check-ip.herokuapp.com/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = client.GetAsync("").Result;
string json = response.Content.ReadAsStringAsync().Result;
dynamic ip = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
string result = ip.ipAddress;
return result;
}
};
public readonly static Func<string> AwsIPCheckStrategy = () => {
using (var client = new HttpClient()) {
client.BaseAddress = new Uri("http://checkip.amazonaws.com/");
string result = client.GetStringAsync("").Result;
return result.TrimEnd('\n');
}
};
You see no class at all. Only three static readonly Delegate-Variables, returning a string - if invoked.
In fact readonly
or static
also is optional - this approach is very flexible
Execute all Strategies in all Approaches
static class StrategyTester {
static public void ExecuteAll() {
ExecuteByInterface();
ExecuteByDelegate();
ExecuteByOverride();
Console.ReadKey();
}
static private void ExecuteByInterface() {
Console.WriteLine("ExecuteByInterface");
IIpCheckStrategy ipChecker;
ipChecker = new DynDnsIPCheckStrategy();
Console.WriteLine(ipChecker.GetExternalIp());
ipChecker = new AwsIPCheckStrategy();
Console.WriteLine(ipChecker.GetExternalIp());
ipChecker = new CustomIpCheckStrategy();
Console.WriteLine(ipChecker.GetExternalIp());
}
static private void ExecuteByDelegate() {
Console.WriteLine("\nExecuteByDelegate");
var dlg = DelegateStrategies.DynDnsIPCheckStrategy;
Console.WriteLine(dlg.Invoke());
dlg = DelegateStrategies.AwsIPCheckStrategy;
Console.WriteLine(dlg());
Console.WriteLine(DelegateStrategies.CustomIpCheckStrategy());
}
static private void ExecuteByOverride() {
Console.WriteLine("\nExecuteByOverride");
var ipChecks = new OverrideStrategy.IpCheckBase[] {
new OverrideStrategy.DynDnsIPCheckStrategy(),
new OverrideStrategy.AwsIPCheckStrategy(),
new OverrideStrategy.CustomIpCheckStrategy() };
foreach (var checker in ipChecks) Console.WriteLine(checker.GetIp());
}
}
I think, that one is self-explainatory. Note, that the ExecuteByInterface()
-Code accesses the code of the base-article (3 Interface-Strategies).
Strategy-Pattern-Approaches in Framework - Samples
- Strategy by Interface
Propably the most used Strategy by Interface might be the IEnumerable<T>
- Interface, which we all use, whenever looping a collection with foreach(...)
. - Override-Strategy
Abstract Override-Strategy is present for example in the Stream
-class, and its derivates (FileStream
, NetworkStream
, GZipStream
, ...).
So each Method, accepting a Stream
-Argument - according to the GoF-Definition - it can be seen as a "client, using an independently variable algorithm" (namely the stream) - Delegate-Strategy
is designed in the List<T>.Sort(Comparison<T>)
- Method. Moreover many Linq-Extensions accept Delegates, to enable different Strategies of Filtering, Sorting, Grouping, and stuff