Page 1 of 1

Design Patterns - Strategy Overview and Application of Strategy Pattern

#1 MentalFloss  Icon User is offline

  • "ADDICTED"[2:5]
  • member icon

Reputation: 526
  • View blog
  • Posts: 1,397
  • Joined: 02-September 09

Posted 10 August 2010 - 02:26 PM

Hello and welcome to this tutorial detailing the widely popular strategy pattern.

First, what does this pattern solve? Well, it allows us to have a variable number of algorithms work against a particular object context.

Hmm... we kind of need to break this down a little.

I took a quick peek at the best website I know of for design patterns (Strategy Pattern) and they use the very excellent example of sorting algorithms.

Honestly, that might be enough of a tutorial for you but they use abstract classes and I have qualms with those so we're going to see how interfaces apply to this concept instead.

Also, instead of sorting algorithms (I hope you already reviewed the link), we'll do state tax on a product.

Let's get started:

Let's create a product interface for any products we may be encountering:

namespace StrategyPatternDemo
{
    /// <summary>
    /// Contract specification for all products in system.
    /// </summary>
    public interface IProduct
    {
        // The name of this product
        string Name { get; }
        
        // The base price of this product
        decimal Price { get; }
    }
}



For our product, let's keep things simple and just attribute a name and a price.

Let's pretend this is a chain of candy stores. We'll have some products that implement the IProduct interface:

namespace StrategyPatternDemo
{
    public class JawBreaker : IProduct
    {
        public string Name { get; private set; }
        public decimal Price { get; private set; }

        public JawBreaker()
        {
            Name = "Jaw Breaker";
            Price = .50M;
        }
    }

    public class BubbleGum : IProduct
    {
        public string Name { get; private set; }
        public decimal Price { get; private set; }

        public BubbleGum()
        {
            Name = "Bubble Gum";
            Price = .75M;
        }
    }

    public class Taffy : IProduct
    {
        public string Name { get; private set; }
        public decimal Price { get; private set; }

        public Taffy()
        {
            Name = "Taffy";
            Price = 1.25M;
        }
    }
}



You're going to notice that I'm actually going to get pretty detailed here with my example. And all just for a few lines of strategy?! Yeah...

See, design patterns are great and all but you really need to get the context behind them. If you don't know WHY you're using it, then you shouldn't be using it.

Anyway, let's create a chain of stores that sell our candy. Since I want different taxes and a quick google search gives me info I need (Sales Taxes) we have plenty of stores to make.

Of course, I'm only going to make 3 or so stores:

  • Arizona = 6%
  • Colorado = 4%
  • California = 8%


Here's our code for an interface:

namespace StrategyPatternDemo
{
    /// <summary>
    /// Contract specification for all stores in system.
    /// </summary>
    public interface IStore
    {
        // The location of the store.
        string Location { get; }

        decimal SalesTax { get; }

        // The total price of a product in a certain store.
        decimal Total { get; }
    }
}



What's that? you may be asking. Well, it's our strategy!

We'll have the interface require a total which is of course the calculated sales tax against the product's base price.

Let's make our stores based on the specification now:

namespace StrategyPatternDemo
{
    // NOTE: Adding 1 to tax automatically adds it together for us.

    public class ArizonaStore : IStore
    {
        public string Location { get; private set; }
        public decimal Total { get; private set; }
        // Arizona's tax is 6%
        public decimal SalesTax
        {
            get { return 1.06M; }
        }

        public ArizonaStore(IProduct product)
        {
            Location = "Arizona";
            Total = product.Price * SalesTax;
        }
    }

    public class ColoradoStore : IStore
    {
        public string Location { get; private set; }
        public decimal Total { get; private set; }

        // Colorado's tax is 4%
        public decimal SalesTax
        {
            get { return 1.04M; }
        }

        public ColoradoStore(IProduct product)
        {
            Location = "Colorado";
            Total = product.Price * SalesTax;
        }
    }

    public class CaliforniaStore : IStore
    {
        public string Location { get; private set; }
        public decimal Total { get; private set; }

        // California's tax is 8%
        public decimal SalesTax
        {
            get { return 1.08M; }
        }

        public CaliforniaStore(IProduct product)
        {
            Location = "California";
            Total = product.Price * SalesTax;
        }
    }
}



Before wrapping up, let's write a record entry:

namespace StrategyPatternDemo
{
    public class RecordEntry
    {
        public string Location { get; set; }
        public string SalesTax { get; set; }
        public string CandyName { get; set; }
        public string BasePrice { get; set; }
        public string TotalPrice { get; set; }

        public override string ToString()
        {
            return String.Format
                   (
                        "{0}, {1}, {2} : {3}, {4}",
                            Location,
                            SalesTax,
                            CandyName,
                            BasePrice,
                            TotalPrice
                    );

        }
    }
}



Well, we've written a lot of little bits here so let's write a Main and hook all this grit together. Before we do though, see what's happening? Based on the store we want, we will get a different total for the very same product. This is because with each store, the algorithm changes. However, calling code doesn't care!

namespace StrategyPatternDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            List<IProduct> CandyList = new List<IProduct>()
            {
                new JawBreaker(),
                new BubbleGum(),
                new Taffy()
            };

            List<RecordEntry> records = new List<RecordEntry>();

            // First Arizona:
            foreach (IProduct product in CandyList)
            {
                IStore store = new ArizonaStore(product);
                records.Add(
                    new RecordEntry()
                    {
                        Location = store.Location,
                        SalesTax = String.Format("{0:P}", store.SalesTax - 1),
                        CandyName = product.Name,
                        BasePrice = String.Format("{0:C}", product.Price),
                        TotalPrice = String.Format("{0:C}", store.Total)
                    }
                );
            }

            // Next Colorado:
            foreach (IProduct product in CandyList)
            {
                IStore store = new ColoradoStore(product);
                records.Add(
                    new RecordEntry()
                    {
                        Location = store.Location,
                        SalesTax = String.Format("{0:P}", store.SalesTax - 1),
                        CandyName = product.Name,
                        BasePrice = String.Format("{0:C}", product.Price),
                        TotalPrice = String.Format("{0:C}", store.Total)
                    }
                );
            }

            // Finally California:
            foreach (IProduct product in CandyList)
            {
                IStore store = new CaliforniaStore(product);
                records.Add(
                    new RecordEntry()
                    {
                        Location = store.Location,
                        SalesTax = String.Format("{0:P}", store.SalesTax-1),
                        CandyName = product.Name,
                        BasePrice = String.Format("{0:C}", product.Price),
                        TotalPrice = String.Format("{0:C}", store.Total)
                    }
                );
            }

            // Last but not least, print:
            foreach (RecordEntry record in records)
            {
                Console.WriteLine(record.ToString());
            }

            // And a pause:
            Console.ReadLine();
        }
    }
}



Quote

Arizona, 6.00 %, Jaw Breaker : $0.50, $0.53
Arizona, 6.00 %, Bubble Gum : $0.75, $0.80
Arizona, 6.00 %, Taffy : $1.25, $1.33
Colorado, 4.00 %, Jaw Breaker : $0.50, $0.52
Colorado, 4.00 %, Bubble Gum : $0.75, $0.78
Colorado, 4.00 %, Taffy : $1.25, $1.30
California, 8.00 %, Jaw Breaker : $0.50, $0.54
California, 8.00 %, Bubble Gum : $0.75, $0.81
California, 8.00 %, Taffy : $1.25, $1.35


And that's another way to work in the strategy pattern.
Here's that original pattern site again for your review: (Strategy Pattern).

Hope this helped. Have fun.

Is This A Good Question/Topic? 3
  • +

Replies To: Design Patterns - Strategy

#2 Sergio Tapia  Icon User is offline

  • D.I.C Lover
  • member icon

Reputation: 1252
  • View blog
  • Posts: 4,168
  • Joined: 27-January 10

Posted 11 August 2010 - 05:36 AM

Thanks a BILLION for writing this tutorial. I think learning (and using) design patterns are the next level for all software developers to aim for. Great job!
Was This Post Helpful? 0
  • +
  • -

#3 cfoley  Icon User is offline

  • Cabbage
  • member icon

Reputation: 1949
  • View blog
  • Posts: 4,048
  • Joined: 11-December 07

Posted 11 August 2010 - 05:35 PM

Nice tutorial. It really comes into its own when you want to completely change the code doing the work. Take power algorithms for example (please excuse the Java)...

public interface Power {
	public int pow(int a, int B)/>;
}

public class MultiplicationPower implements Power {
	public int pow(int a, int B)/> {
		int answer = 1;
		for(int i = 0; i < b; i++) {answer *= a;}
		return answer;
	}
}

public class AdditionPower implements Power {
	public int pow(int a, int B)/> {
		int answer = 0;
		for(int i = 0; i < b; i++) {
			for(int j = 0; j < a; j++) {
				answer += a;
			}
		}
		return answer;
	}
}

public class RecursivePower implements Power {
	public int pow(int a, int B)/> {
		if (b == 0) {return 1;}
		if (b == 1) {return a;}
		int x = b / 2;
		return pow(a, x) * pow(a, b - x);
	}
}

public class CachedPower implements Power {
	
	private Map<String,Integer> cache = new HashMap<String, Integer>();

	public int pow(int a, int B)/> {
		
		if (b == 0) {return 1;}
		if (b == 1) {return a;}

		String key = makeKey(a, B)/>;
		if (cache.containsKey(key)) {return cache.get(key);}
		
		int x = b / 2;
		int answer = pow(a, x) * pow(a, b - x);
		cache.put(key, answer);
		return answer;

		
	}
	
	private String makeKey(int a, int B)/> {
		return a + ":" + b;
	}
	
}

Was This Post Helpful? 0
  • +
  • -

#4 Esoo  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 2
  • Joined: 27-November 09

Posted 30 August 2010 - 11:49 AM

Thanks for this nice tutorial :bigsmile:

I think the Head First in Design Pattern is an awsom and interesting book. :bigsmile:

I am reading in it now.
but i have a Question :-
why after i read the chapter in Head First in Design Pattern i feel that i forget all things i read ??

Thanks again for this great tutorial :) :^:
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1