Gal Segal's Blog

Thoughts of a programmer with a soul

Jul 18

Implementations For Strategy Pattren

Tags:

strategy

Strategy is a well known design patten. It was introduced by GOF way back. It aims to find an extensible and maintainable solution for long “if” or “switch” blocks, filled with logic. As said by its’ creators:”Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.”

Strategy holds 3 major players:

  1. Strategy – the contract for each case.
  2. Concrete  Рthe implementations of the contract
  3. Context – the object that invokes the right concrete implementation

Lets take for example a cashier application. The user user can deposit and withdraw money from several online services. Each service as its own API and logic for these transactions.

We have 3 services to work with: Paypal, MoneyBookers and NetTeller. We need to identify the customer’s favorite service and invoke the relevant method for withdraw or deposit.

We define a Strategy :

public interface IPaymentService
{
     string Deposit(decimal amount);
     string Withdraw(decimal amount);
}

and 3 concrete implementations:

public class Paypal : IPaymentService
{
  public string Deposit(decimal amount)
  {
    return string.Format("Deposit from Paypal ${0}", amount);
  }

  public string Withdraw(decimal amount)
  {
    return string.Format("Withdraw from Paypal ${0}", amount);
  }
}

public class NetTeller : IPaymentService
{
  public string Deposit(decimal amount)
  {
    return string.Format("Deposit from NetTeller ${0}", amount);
  }

  public string Withdraw(decimal amount)
  {
    return string.Format("Withdraw from NetTeller ${0}", amount);
  }
}

public class MoneyBookers : IPaymentService
{
  public string Deposit(decimal amount)
  {
    return string.Format("Deposit from MoneyBookers ${0}", amount);
  }

  public string Withdraw(decimal amount)
  {
    return string.Format("Withdraw from MoneyBookers ${0}", amount);
  }
}

First Attempt: Switch Statement

Switch statement is the most basic implementation:


public string Deposit(ePaymentService serviceName, decimal amount)
{
  IPaymentService service = GetService(serviceName);
  return service != null ? service.Deposit(amount) : "no service found";
}

public string Withdraw(ePaymentService serviceName, decimal amount)
{
  IPaymentService service = GetService(serviceName);
  return service != null ? service.Withdraw(amount) : "no service found";
}

private IPaymentService GetService(ePaymentService serviceName)
{
  switch (serviceName)
  {
    case ePaymentService.Paypal:
        return new Paypal();
    case ePaymentService.MoneyBookers:
        return new MoneyBookers();
    case ePaymentService.NetTeller:
        return new NetTeller();
    default:
        return null;
   }
}

The big problem with this approach is that every new service that will be added to our application will need a new switch case in the invoking code.
This approach is the opposite of the open closed principle that says: “software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification“.

Second Attempt: Dictionary

This time, we predefine a dictionary and use it to invoke the proper service:

public class DictionaryCashierContext
{
#region Private Members

private Dictionary<ePaymentService, IPaymentService> _services;

#endregion

#region Ctor

public DictionaryCashierContext()
{
_services = new Dictionary<ePaymentService, IPaymentService>();
_services.Add(ePaymentService.MoneyBookers, new MoneyBookers());
_services.Add(ePaymentService.NetTeller, new NetTeller());
_services.Add(ePaymentService.Paypal, new Paypal());
}

#endregion

#region Methods

public string Deposit(ePaymentService serviceName, decimal amount)
{
IPaymentService service = GetService(serviceName);
return service != null ? service.Deposit(amount) : "no service found";
}

public string Withdraw(ePaymentService serviceName, decimal amount)
{
IPaymentService service = GetService(serviceName);
return service != null ? service.Withdraw(amount) : "no service found";
}

private IPaymentService GetService(ePaymentService serviceName)
{
return _services.ContainsKey(serviceName) ? _services[serviceName] : null;
}

#endregion
}

We have more generic way of invoking the service but we are still dependent on hard coding the dictionary. Not good enough.

Third Attempt: Reflection

OK. This is getting better. We use the enum for finding the proper service:


public class ReflectionCashierContext
{
#region Private Members

private const string SERVICES_NS = "StrategyCashier.Concrete";

#endregion

#region Methods

public string Deposit(ePaymentService serviceName, decimal amount)
{
IPaymentService service = GetService(serviceName);
return service != null ? service.Deposit(amount) : "no service found";
}

public string Withdraw(ePaymentService serviceName, decimal amount)
{
IPaymentService service = GetService(serviceName);
return service != null ? service.Withdraw(amount) : "no service found";
}

private IPaymentService GetService(ePaymentService serviceName)
{
string typeName = string.Format("{0}.{1}", SERVICES_NS, serviceName.ToString());
IPaymentService service = Activator.CreateInstance(Type.GetType(typeName)) as IPaymentService;
return service;
}

#endregion
}

It looks better: the only hard dependency is the ePaymentService enum.

At this point I stop. But before leaving, I will remind 2 additional ways:

  1. Unity or any other DI container: we can register the concrete implementations in the container and use strings (like in Unity to find the right one.
  2. MEF: even better – use a dictionary look-up and have the ability to load in run-time any service we want, without the need for compiling.

Happy Coding :)

Back to top