65.9K
CodeProject is changing. Read more.
Home

Design Patterns: Strategy (Alternative)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (3 votes)

Oct 23, 2015

CPOL

2 min read

viewsIcon

13232

downloadIcon

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

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

/*
   * 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

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