Click here to Skip to main content
15,880,608 members
Articles / Programming Languages / C#
Alternative
Article

Design Patterns: Strategy (Alternative)

Rate me:
Please Sign up or sign in to vote.
4.75/5 (3 votes)
23 Oct 2015CPOL2 min read 12.8K   71   5   3
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)


Written By
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionMy vote of 5! Pin
jediYL26-Oct-15 16:38
professionaljediYL26-Oct-15 16:38 
GeneralStrategy 1 - Doesn't have to be ABC Pin
marcus obrien23-Oct-15 18:25
marcus obrien23-Oct-15 18:25 
Hi, I don't think strategy one has to be an ABC, it could be a standard class with a virtual function, this base class virtual (non-pure virtual) contains the default behaviour - so unlike an pure virtual function it is a virtual function that has a definition, this in effect is the default behaviour (it will be executed if the class is not overridden - even if the default behaviour throws an invalid or not implemented exception).
Answerthx Pin
Mr.PoorEnglish23-Oct-15 22:13
Mr.PoorEnglish23-Oct-15 22:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.