Monday 27 August 2018

Strategy pattern

The strategy pattern is defined as "The strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm independently from clients that use it." What that means is that instead of our class having a defined function to accomplish something, instead it has an abstraction that describes what the result and the signature of the method is, this let's us leverage various versions of the "abstraction" based on the situation.

Here's the concept as a UML representation.


What this means is that our Context class has a private field of type IStrategy; as one can deduce this field is set in the constructor; that private strategy field is then used within the public Run method to execute the Go method from a concrete implementation of our IStrategy interface. In the above UML representation there are two implementations of the IStrategy Interface: StrategyOne and StrategyTwo. Either StrategyOne or StrategyTwo can be supplied to the Context class to allow the interchangeability of our algorithm.

Let's take a look at a concrete example with shapes.

Traditionally when you learn about Object Oriented Programming you may come across something like the following.


Now the above is perfectly valid you create an abstract base Shape class which contains width and height properties, and abstract methods that are overridden in subclasses. makes sense right? but the question is do we really need this hierarchy? well let's take a look at the following


as you can see this looks far more complex than the "Simple" inheritance model, but let's investigate a bit further, firstly let's take a look at our inheritance solution.

using System;

namespace pav.shapesOOP
{
    abstract class Shape
    {
        protected int[] args;
        public abstract double GetArea();
        public abstract double GetPerimeter();
    }

    class Rectangle : Shape
    {
        public Rectangle(int[] args) => base.args = args;
        public override double GetArea() => args[0] * args[1];
        public override double GetPerimeter() => 2 * args[0] + 2 * args[1];
    }

    class Circle : Shape
    {
        public Circle(int[] args) => base.args = args;
        public override double GetArea() => Math.Round(Math.PI * Math.Pow((args[0] / 2), 2), 2);
        public override double GetPerimeter() => Math.Round(Math.PI * args[0], 2);
    }

    class RightTriangle : Shape
    {
        public RightTriangle(int[] args) => base.args = args;
        public override double GetArea() => (args[0] * args[1]) / 2;
        public override double GetPerimeter()
            => Math.Sqrt(Math.Pow(args[0], 2) + Math.Pow(args[1], 2)) + args[0] + args[1];
    }

    class Program
    {
        static void Main(string[] args)
        {
            var c = new Circle(new[] { 10 });
            Console.WriteLine($"Circle:\t\t A:{c.GetArea()}\t P:{c.GetPerimeter()}");

            var r = new Rectangle(new[] { 5, 4 });
            Console.WriteLine($"Rectangle:\t A:{r.GetArea()}\t\t P:{r.GetPerimeter()}");

            var t = new RightTriangle(new[] { 3, 4 });
            Console.WriteLine($"RightTriangle:\t A:{t.GetArea()}\t\t P:{t.GetPerimeter()}");
        }
    }
}


Now we can see that every time we are going to introduce a new shape we have to inherit from our Shape base class and inherit our abstract methods.

Next let's take a look at our Composite Solution

using System;

namespace pav.StrategyPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            var c = new Shape(new[] { 10,10 }, new CircleAreaStrategy() , new CirclePerimeterStrategy() );
            Console.WriteLine($"Circle:\t\t A:{c.getArea()}\t P:{c.getPerimeter()}");

            var r = new Shape(new[] { 5, 4 }, new RectanlgeAreaStrategy(), new RectanglePerimeterStrategy());
            Console.WriteLine($"Rectangle:\t A:{r.getArea()}\t\t P:{r.getPerimeter()}");

            var t = new Shape(new[] { 3, 4 }, new TriangleAreaStrategy(), new RightTrianglePerimeterStrategy());
            Console.WriteLine($"RightTriangle:\t A:{t.getArea()}\t\t P:{t.getPerimeter()}");
        }
    }

    class Shape
    {
        IAreaStrategy areaStrategy;
        IPerimeterStrategy perimeterStrategy;

        protected int[] args;

        public Shape(int[] args, IAreaStrategy areaStrategy, IPerimeterStrategy perimeterStrategy)
        {
            this.args = args;
            this.areaStrategy = areaStrategy;
            this.perimeterStrategy = perimeterStrategy;
        }

        public double getArea() => areaStrategy.GetArea(args);
        public double getPerimeter() => perimeterStrategy.GetPerimeter(args);
    }

    interface IAreaStrategy { double GetArea(int[] args); }

    public class TriangleAreaStrategy : IAreaStrategy {
        public double GetArea(int[] args) =>args[0] * args[1] / 2;
    }

    public class RectanlgeAreaStrategy : IAreaStrategy {
        public double GetArea(int[] args) => args[0] * args[1];
    }

    public class CircleAreaStrategy : IAreaStrategy {
        public double GetArea(int[] args) => Math.Round(args[0]/2 * Math.PI,2);
    }

    interface IPerimeterStrategy { double GetPerimeter(int[] args); }

    public class CirclePerimeterStrategy : IPerimeterStrategy {
        public double GetPerimeter(int[] args) => Math.Round(Math.PI * args[0], 2);
    }

    public class RectanglePerimeterStrategy : IPerimeterStrategy {
        public double GetPerimeter(int[] args) => 2 * args[0] + 2 * args[1];
    }

    public class RightTrianglePerimeterStrategy : IPerimeterStrategy {
        public double GetPerimeter(int[] args)
           => Math.Sqrt(Math.Pow(args[0], 2) + Math.Pow(args[1], 2)) + args[0] + args[1];
    }
}

now our code may appear more complex, but it's not and it's far simpler to extend. What i mean is for example to use a very trivial example let's say that we wanted to introduce a square, in our inheritance approach we'd create a new type that inherits from Shape and then overrides the abstract GetArea() and GetPerimeter() functions, but in our composite solution we'd create new AreaStrategy and PerimeterStragey classes that solved our problems.

One Caveat before the next section, it has been a long time since i've done highschool so my trigonometry might be off, however it's not the point of this entry.

Now instead of a square let's say we have a non right angle triangle, and we have the Base, the Height and one angle like so


well using our composition strategy we could reuse our Area of a triangle strategy and just create a new one for perimeter.

using System;

namespace pav.StrategyPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            //Diameter
            var c = new Shape(new[] { 10 }, new CircleAreaStrategy() , new CirclePerimeterStrategy() );
            Console.WriteLine($"Circle:\t\t A:{c.getArea()}\t P:{c.getPerimeter()}");

            //Base, Height
            var r = new Shape(new[] { 5, 4 }, new RectanlgeAreaStrategy(), new RectanglePerimeterStrategy());
            Console.WriteLine($"Rectangle:\t A:{r.getArea()}\t\t P:{r.getPerimeter()}");

            //Base, Height
            var rt = new Shape(new[] { 3, 4 }, new TriangleAreaStrategy(), new RightTrianglePerimeterStrategy());
            Console.WriteLine($"RightTriangle:\t A:{rt.getArea()}\t\t P:{rt.getPerimeter()}");

            //Base, Height, Angle
            var t= new Shape(new[] { 4, 2, 55 }, new TriangleAreaStrategy(), new TrianglePerimeterStrategy());
            Console.WriteLine($"RightTriangle:\t A:{t.getArea()}\t\t P:{t.getPerimeter()}");
        }
    }
   
    class Shape
    {
        IAreaStrategy areaStrategy;
        IPerimeterStrategy perimeterStrategy;

        protected int[] args;

        public Shape(int[] args, IAreaStrategy areaStrategy, IPerimeterStrategy perimeterStrategy)
        {
            this.args = args;
            this.areaStrategy = areaStrategy;
            this.perimeterStrategy = perimeterStrategy;
        }

        public double getArea() => areaStrategy.GetArea(args);
        public double getPerimeter() => perimeterStrategy.GetPerimeter(args);
    }

    interface IAreaStrategy { double GetArea(int[] args); }

    public class TriangleAreaStrategy : IAreaStrategy {
        public double GetArea(int[] args) =>args[0] * args[1] / 2;
    }

    public class RectanlgeAreaStrategy : IAreaStrategy {
        public double GetArea(int[] args) => args[0] * args[1];
    }

    public class CircleAreaStrategy : IAreaStrategy {
        public double GetArea(int[] args) => Math.Round(args[0]/2 * Math.PI,2);
    }

    interface IPerimeterStrategy { double GetPerimeter(int[] args); }

    public class CirclePerimeterStrategy : IPerimeterStrategy {
        public double GetPerimeter(int[] args) => Math.Round(Math.PI * args[0], 2);
    }

    public class RectanglePerimeterStrategy : IPerimeterStrategy {
        public double GetPerimeter(int[] args) => 2 * args[0] + 2 * args[1];
    }

    public class RightTrianglePerimeterStrategy : IPerimeterStrategy {
        public double GetPerimeter(int[] args)
           => Math.Sqrt(Math.Pow(args[0], 2) + Math.Pow(args[1], 2)) + args[0] + args[1];
    }

    class TrianglePerimeterStrategy : IPerimeterStrategy
    {
        public double GetPerimeter(int[] args)
        {
            var Base = args[0];
            var Height = args[1];
            var Theta = (Math.PI / 180) * args[2];

            var SideA = Height / Math.Sin(Theta);
            var SideB = Math.Sqrt(Math.Pow(SideA, 2) + Math.Pow(Base, 2) - 2 * Base * SideA * Math.Cos(Theta));
            return Math.Round(Base + SideA + SideB);
        }
    }

}

now as i've mentioned before Trig was a long time ago, but the point is that with our strategy approach we just create a new class that can handle the new perimeter approach and we could reuse the original area strategy without having to build a complicated inheritance hierarchy.