Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Design Patterns: Strategy (Alternative)

4.75/5 (3 votes)
23 Oct 2015CPOL2 min read 13.2K   72  
The Strategy-Pattern does not nessecarily require Interfaces - two other Approaches

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

C#
abstract class IpCheckBase {
   //abstract Strategy-baseclasses can provide some general logic and Infrastructure to its Derivates

   public string GetIp() {     //  provide general logic to Derivates: create / destroy HttpClient
      using (var client = new HttpClient()) {
         return GetIpCore(client);
      }
   }
   protected abstract string GetIpCore(HttpClient client); // placeholder for the specific logic
}

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

C#
/*
   * strategy-delegates can be located everywhere, can be static or not, or anonymous. Only a matching signature is obligatory
   */
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

C#
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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)