Meet our first design pattern super Hero - Mr. Strategy!!

       Mr. Strategy is our design pattern
       superhero this week!!!


Definition: Strategy pattern defines a family of algorithms, encapsulates each one and makes them interchangeable. Strategy lets the algorithms vary independent from clients that use it                                                              

Given below is the UML representation of Mr.Strategy's modus operandi:




The following are the important players involved in Mr.Strategy's strategy to defeat the evil guys
Context
This refers to the class that will contain the base Strategy Interface which in our case is "IStrategy". The Context would know only about "IStartegy" and will not know about the classes implementing the IStartegy interface. It is also possible to have a parent class instead of the interface. Choose a class if you need a default implementation that all child classes should implement.Through the "Strategy" property, it would be possible to vary the Strategy type used by the context at runtime. 

IStrategy
This defines the "operation" or the algorithm that varies for every class that implements this interface. This in our case is the "DoOperation()" method.

ConcreteStrategy_1 && ConcreteStrategy_2
This classes are the implementations of the IStrategy interface. These would be instantiated and assigned to the Context class by another Class(This can be a Factory/Control class).  As you can see, it is possible to vary the algorithm used by the Context class for the "DoOperation()" method independent from the Context class. Moreover this can be done at run time instead of compile time.

Case Study
Okay to understand  this better consider the following implementation of a method "CalculateShippingCost(): in a Cart class. The class diagram is shown below:


The code for the CalculateShippingCost() Method  is shown below

public double CalculateShippingCost(string country)
{
   
double baseCost;
   double priceCost = 0.0;
   double weight = this.CalculateProductWeight();

 
switch (country)
  {
   
case ("USA"):
    {
       baseCost = 3.0;
       priceCost = baseCost * weight;
       break;
    }
    
    case ("Canada"):
    {
       baseCost = 4.0;
       priceCost = baseCost * weight;
       break;
    }
 }
  return
priceCost;
}



Now here are some problems with this implementation

  • Every time a new country is added, the CalculateShippingCost() method has to be updated to
    encounter for that change.
  • The "base cost" for a countries shipping cost might change frequently, and the code requires
    frequent  modification
  • New calculations for shipping cost (like promotional features, type of shipping) for each country
    might make the code in the switch statement more complex, and difficult to maintain

When Mr.Strategy applies his powers to the  above example, the class diagram now becomes



Here are our main players that map with the original players in the Strategy definition

Context   ShoppingCart
Strategy   IShippingStrategy
Concrete Implementations    UsaShippingStrategy, CanadaShippingStrategy
Client setting the Strategy WebApplication

As we can see, someone has to take the property of setting the appropriate "shipping strategy " in the Shopping cart class. In our example it is the "Web Application". One of the drawback/feature of applying the Strategy pattern is the need of some class to know about the different strategies and set them in the Context class.

The code for CalculateShippingCost in ShoppingCart now looks like

public double CalculateShippingCost()
{
     double productsWeight = this.CalculateProductsWeight();
     return this.ShippingStrategy.CalculateShippingCost(productsWeight);

}


And the Shipping Strategy is now set by the Web Application. The example below uses a switch statement, but it is possible to read this from a configuration file also.

public void UpdateShipping(string countrySelected)
{
 
switch (countrySelected)
  {
   
case ("USA"):
    {
       shoppingCart.ShippingStrategy =
this.usaShippingStrategy;
       break;
     }
 
    case ("Canada"):
    {
       shoppingCart.ShippingStrategy =
this.canadaShippingStrategy;
       break;
    }
 
}
}

Advantages of using services of Mr.Strategy
So what have we gained ?

  • We have encapsulated the algorithm for calculating the shipping cost for various 
    countries into a separate set of classes. By doing so we can now vary the algorithm
    used by the ShopingCart at runtime
  • Also the carts job is now to calculate the weight of the products and delegate the 
    calculation to the ShippingStrategy class. Any changes to the Shipping rate calculation
    algorithms do not require any changes to the Cart class.
  • We have got rid of the messy Switch statement, and not only does this make the code 
    easy to maintain and extend, but also easy to read
  • We can now reuse this shipping price code across otehr applications. A Shipping Calculator application can use this to display to users the shipping rates for various countries, even before they decide to buy anything.


Refactoring to the strategy pattern
You can also refactor existing code to the STrategy pattern to make it more easy to maintain and extend. Martin Fowler and Joshua KereieVsky suggest using this pattern for resolving a code smell called "Switch Statement". This refers to a piece of code that has a lot of conditional logic (in the form of switch/if statements)

Note: If you have a large no of case statements (like 200 case statements) that just returns a literal value based on the switch value compared, you are better of using a table driven approach. You can load all the values and the keys (the value that you pass in the  switch statement) in a hash table. This will reduce lookup time and increase performance !

Real World Examples

The IComparer strategy defined in System.Collections namespace provides the way the objects in a collection have to be sorted. The algorithm that can be implemented by the objects go into the "Compare()" method. The definition of the compare method is given below

                               public int Compare(object x, object y);


It is possible for you to provide concrete implementations of this strategy, like ProductComparer that would have the following implementation that compares and sorts products based on their product Name.

class ProductComparer : IComparer
{
    public int Compare(object firstProduct, object secondProduct)
    {

       Product
product1 = firstProduct as Product;
       Product product2 = secondProduct as Product;

       return product1.Name.CompareTo(product2.Name);
    }

}



And if you had an ArrayList called "productsCollection" containing Product objects, you can sort them as follows

                                     productsCollection.Sort(new ProductComparer());



Mr.Startegy's Relatives
These include : Mr.State and Ms. Template Method

Okay, that covers up this weeks discussion about our superhero Next week we will cover our second superhero of the week --> Ms. Decorator

 

What did you think of this article?




Trackbacks
  • No trackbacks exist for this post.
Comments
  • No comments exist for this post.
Leave a comment

Submitted comments are subject to moderation before being displayed.

 Name

 Email (will not be published)

 Website

Your comment is 0 characters limited to 3000 characters.