Showing posts with label Strategy Design Pattern. Show all posts
Showing posts with label Strategy Design Pattern. Show all posts

Tuesday, November 12, 2013

Strategy Design Pattern

Hello

There is pretty simple pattern, which make encapsulation of algorithms

Class diagrams, from different points of view.

Class Diagram

Fig 1:class diagram without associations

Fig 2: class diagram with client associations

Fig 3: class diagram with associations inside of Startegy pattern

Explanations of class diagrams from point of view of Associations:
Fig 1: Nothing to say
Fig 2: Client should know only Context class because of client instantiate it. Client should know about all implemented algorithms, because of client instantiate context with specific algorithm.
Fig 3: Inside of pattern implementation, only Context should know about IStrategy, because of Contexts instantiated with IStrategy interface.

Code is example of calculations algorithms:

namespace StrategyDP
{
    public class Calculator
    {
        private ICalculation _calc;

        public Calculator(ICalculation calc)
        {
            _calc = calc;
        }

        public double Calculate(double a, double b)
        {
            return _calc.Calculate(a, b);
        }
    }

    public interface ICalculation
    {
        double Calculate(double a, double b);
    }

    public class AddAlg : ICalculation
    {
        public double Calculate(double a, double b)
        {
            return a + b;
        }
    }

    public class MinusAlg : ICalculation
    {
        public double Calculate(double a, double b)
        {
            return a - b;
        }
    }

    public class DevAlg : ICalculation
    {
        public double Calculate(double a, double b)
        {
            return a / b;
        }
    }

    public class MulAlg : ICalculation
    {
        public double Calculate(double a, double b)
        {
            return a * b;
        }
    }
}

Using (as UniTest):
Calculator calc = new Calculator(new AddAlg());
Assert.AreEqual(calc.Calculate(6, 2), 8);
calc = new Calculator(new MinusAlg());
Assert.AreEqual(calc.Calculate(6, 2), 4);
calc = new Calculator(new DevAlg());
Assert.AreEqual(calc.Calculate(6, 2), 3);
calc = new Calculator(new MulAlg());
Assert.AreEqual(calc.Calculate(6, 2), 12);

That's all

Thursday, November 7, 2013

Refactoring classes to Strategy and State design patterns

Hi,
Today i try use 2 behavioral design pattern: Strategy and State design patterns. About this patterns there a lot of stuff in net. I wish try use some refactoring, for moving poor designed class to better designed, by using Strategy and State design patterns.

In my example i do calculations of taxes for products depend on season. For example, in Summer, tax for product, which costs 100 units not same, as a tax in Winter for product, which costs 150 units. So, there is system of tax calculation, depends on season and product price.

Lets design!

Bang!

namespace SeasonTaxesNoDP

{
    public class SeasonTaxes
    {
        public enum Season { Spring, Summer, Autumn, Winter, }

        Season _season;

        public SeasonTaxes()
        {
            _season = Season.Spring;
        }

        public void NextSeason()
        {
            switch (_season)
            {
                case Season.Autumn:
                    _season = Season.Winter;
                    break;
                case Season.Spring:
                    _season = Season.Summer;
                    break;
                case Season.Summer:
                    _season = Season.Autumn;
                    break;
                case Season.Winter:
                    _season = Season.Spring;
                    break;
            }
        }

        public void PreviousSeason()
        {
            switch (_season)
            {
                case Season.Autumn:
                    _season = Season.Summer;
                    break;
                case Season.Spring:
                    _season = Season.Winter;
                    break;
                case Season.Summer:
                    _season = Season.Spring;
                    break;
                case Season.Winter:
                    _season = Season.Autumn;
                    break;
            }
        }

        public double CalculateTax(double productPrice)
        {
            switch (_season)
            {
                case Season.Autumn:
                    if (productPrice > 180)
                    {
                        return productPrice * 0.32 + 11.2;
                    }
                    else
                    {
                        return productPrice * 0.32 + 13.2;
                    }
                case Season.Spring:
                    if (productPrice > 170)
                    {
                        return productPrice * 0.52 + 15.2;
                    }
                    else
                    {
                        return productPrice * 0.42 + 17.2;
                    }
                case Season.Summer:
                    if (productPrice > 300)
                    {
                        return productPrice * 0.72 + 21.2;
                    }
                    else
                    {
                        return productPrice * 0.42 + 23.2;
                    }
                case Season.Winter:
                    if (productPrice > 220)
                    {
                        return productPrice * 0.342 + 11.2;
                    }
                    else
                    {
                        return productPrice * 0.562 + 16.2;
                    }
            }
            return 0.0;
        }
    }
}

Using:
        private static void f4()
        {
            SeasonTaxesNoDP.SeasonTaxes season = new SeasonTaxesNoDP.SeasonTaxes();
            season.CalculateTax(345);

            season.NextSeason();
            season.CalculateTax(567);
        }

SeasonTaxes is an Finite-state machine (FSM) of seasons + system of calculations, so it do 2 things. So it have 2 axis of changes: if there change system of calculations it will change or if there change in FSM it will change too. So, it violates The Single Responsibility Principle (SRP).

This code is hard to understand and hard to maintenance.

If i will add new season, it will violate Open - Close principle (OCP)
If i will remove season, same.
Same about system of calculations.

Lets do refactoring.
I will move to State design pattern, so i wish move the FSM out from the class. It will remove one axis of potential changes: changes in Seasons switch machine


Code:
namespace SeasonTaxesStateDP
{
    public abstract class SeasonState
    {
        public abstract void NextSeason(SeasonTaxes seasonTaxes);
        public abstract void PreviousSeason(SeasonTaxes seasonTaxes);
        public abstract double CalculateTax(double productPrice);
    }

    public class SpringSeasonState : SeasonState
    {
        public override void NextSeason(SeasonTaxes seasonTaxes) { seasonTaxes.State = new SummerSeasonState(); }
        public override void PreviousSeason(SeasonTaxes seasonTaxes) { seasonTaxes.State = new WinterSeasonState(); }

        public override double CalculateTax(double productPrice)
        {
            if (productPrice > 170)
            {
                return productPrice * 0.52 + 15.2;
            }
            else
            {
                return productPrice * 0.42 + 17.2;
            }
        }
    }

    public class AutumnSeasonState : SeasonState
    {
        public override void NextSeason(SeasonTaxes seasonTaxes) { seasonTaxes.State = new WinterSeasonState(); }
        public override void PreviousSeason(SeasonTaxes seasonTaxes) { seasonTaxes.State = new SummerSeasonState(); }

        public override double CalculateTax(double productPrice)
        {
            if (productPrice > 180)
            {
                return productPrice * 0.32 + 11.2;
            }
            else
            {
                return productPrice * 0.32 + 13.2;
            }
        }
    }

    public class SummerSeasonState : SeasonState
    {
        public override void NextSeason(SeasonTaxes seasonTaxes) { seasonTaxes.State = new AutumnSeasonState(); }
        public override void PreviousSeason(SeasonTaxes seasonTaxes) { seasonTaxes.State = new SpringSeasonState(); }

        public override double CalculateTax(double productPrice)
        {
            if (productPrice > 300)
            {
                return productPrice * 0.72 + 21.2;
            }
            else
            {
                return productPrice * 0.42 + 23.2;
            }
        }
    }

    public class WinterSeasonState : SeasonState
    {
        public override void NextSeason(SeasonTaxes seasonTaxes) { seasonTaxes.State = new SpringSeasonState(); }
        public override void PreviousSeason(SeasonTaxes seasonTaxes) { seasonTaxes.State = new AutumnSeasonState(); }

        public override double CalculateTax(double productPrice)
        {
            if (productPrice > 220)
            {
                return productPrice * 0.342 + 11.2;
            }
            else
            {
                return productPrice * 0.562 + 16.2;
            }
        }
    }

    public class SeasonTaxes
    {
        public SeasonState State { get; set; }

        public SeasonTaxes()
        {
            State = new SpringSeasonState();
        }

        public void NextSeason()
        {
            State.NextSeason(this);
        }

        public void PreviousSeason()
        {
            State.PreviousSeason(this);
        }

        public double CalculateTax(double productPrice)
        {
            return State.CalculateTax(productPrice);
        }
    }
}


Class SeasonTaxes get smaller and simpler. It is delegates responsibilities to SeasonState implementations. 
But, each implementation of SeasonState  violates OCP and SRP. 

So, i did not like calculations in State classes. 
It is looks, like a mess.
I wish move tax calculations out from FSM.
Lets do refactoring to Strategy design pattern:


Code:
namespace SeasonTaxesStateStrategyDP
{
    public interface IStrategyTax
    {
        double CalculateTax(double productPrice);
    }

    public class SpringStrategyTax : IStrategyTax
    {
        public double CalculateTax(double productPrice)
        {
            if (productPrice > 170)
            {
                return productPrice * 0.52 + 15.2;
            }
            else
            {
                return productPrice * 0.42 + 17.2;
            }
        }
    }

    public class AutumnStrategyTax : IStrategyTax
    {
        public double CalculateTax(double productPrice)
        {
            if (productPrice > 180)
            {
                return productPrice * 0.32 + 11.2;
            }
            else
            {
                return productPrice * 0.32 + 13.2;
            }
        }
    }

    public class SummerStrategyTax : IStrategyTax
    {
        public double CalculateTax(double productPrice)
        {
            if (productPrice > 300)
            {
                return productPrice * 0.72 + 21.2;
            }
            else
            {
                return productPrice * 0.42 + 23.2;
            }
        }
    }

    public class WinterStrategyTax : IStrategyTax
    {
        public double CalculateTax(double productPrice)
        {
            if (productPrice > 220)
            {
                return productPrice * 0.342 + 11.2;
            }
            else
            {
                return productPrice * 0.562 + 16.2;
            }
        }
    }

    public abstract class SeasonState
    {
        public abstract void NextSeason(SeasonTaxes seasonTaxes);
        public abstract void PreviousSeason(SeasonTaxes seasonTaxes);

        protected IStrategyTax _strategyTax;

        public double CalculateTax(double productPrice)
        {
            return _strategyTax.CalculateTax(productPrice);
        }
    }

    public class SpringSeasonState : SeasonState
    {
        public override void NextSeason(SeasonTaxes seasonTaxes) { seasonTaxes.State = new SummerSeasonState(); }
        public override void PreviousSeason(SeasonTaxes seasonTaxes) { seasonTaxes.State = new WinterSeasonState(); }
    }

    public class AutumnSeasonState : SeasonState
    {
        public override void NextSeason(SeasonTaxes seasonTaxes) { seasonTaxes.State = new WinterSeasonState(); }
        public override void PreviousSeason(SeasonTaxes seasonTaxes) { seasonTaxes.State = new SummerSeasonState(); }
    }

    public class SummerSeasonState : SeasonState
    {
        public override void NextSeason(SeasonTaxes seasonTaxes) { seasonTaxes.State = new AutumnSeasonState(); }
        public override void PreviousSeason(SeasonTaxes seasonTaxes) { seasonTaxes.State = new SpringSeasonState(); }
    }

    public class WinterSeasonState : SeasonState
    {
        public override void NextSeason(SeasonTaxes seasonTaxes) { seasonTaxes.State = new SpringSeasonState(); }
        public override void PreviousSeason(SeasonTaxes seasonTaxes) { seasonTaxes.State = new AutumnSeasonState(); }
    }

    public class SeasonTaxes
    {
        public SeasonState State { get; set; }

        public SeasonTaxes()
        {
            State = new SpringSeasonState();
        }

        public void NextSeason()
        {
            State.NextSeason(this);
        }

        public void PreviousSeason()
        {
            State.PreviousSeason(this);
        }

        public double CalculateTax(double productPrice)
        {
            return State.CalculateTax(productPrice);
        }
    }
}

that's it