Thursday 13 September 2018

Factory Method Pattern

In a previous post I mentioned the "Simple Factory Pattern" which in essence is the same thing as  the "Factory Method Pattern" with the only real difference being the fact that in this case the factory adheres to an abstraction. Before we get into the factory let's take a look at our data model.

interface IShape
{
    double[] Args { get; set; }
    double GetArea();
    double GetPerimeter();
    void Print();
}

class SquareIShape
{
    public double[] Args { get; set; }
    public Square(double s) => Args = new double[] { s };

    public double GetArea() => Math.Pow(Args[0], 2);
    public double GetPerimeter() => Args[0] * 4;

    public void Print()
    {
        Console.Write($"I am a {this.GetType().Name}");
        Console.WriteLine($"Area:{GetArea()}\tPerimeter:{GetPerimeter()}\r\n");
    }
}

class RectangleIShape
{
    public double[] Args { get; set; }
    public Rectangle(double width, double height) => Args = new double[] { width, height };

    public double GetArea() => Args[0] * Args[1];
    public double GetPerimeter() => Args[0] * 2 +  Args[1] * 2;
    public void Print()
    {
        Console.WriteLine($"I am a {this.GetType().Name}");
        Console.WriteLine($"Area:{GetArea()}\tPerimeter:{GetPerimeter()}\r\n");
    }
}

class TriangleIShape
{
    public double[] Args { get; set; }
    public Triangle(double adjacent, double theta, double hypotenuse)
        => Args = new double[] { adjacent, theta, hypotenuse };

    public double GetArea()
    {
        var adjacent = Args[0];
        var theta = ToRadians(Args[1]);
        var hypotenuse = Args[2];
        var height = Math.Sin(theta) * hypotenuse;

        return adjacent * height / 2;

    }
    public double GetPerimeter()
    {
        var adjacent = Args[0];
        var theta = ToRadians(Args[1]);
        var hypotenuse = Args[2];
        var height = Math.Sin(theta) * hypotenuse;

        var alpha = ToRadians(180 - 90 - Args[1]);
        var subBase = Math.Sin(alpha) * hypotenuse;
        var diffBase = adjacent - subBase;

        var opposite = Math.Sqrt(Math.Pow(height, 2) + Math.Pow(diffBase, 2));

        return adjacent + hypotenuse + opposite;

    }
}

Now we have a base shape interface that defines a couple of functions and a storage mechanism for our shape values let's make notice that all of our implementations of the IShape interface leverage a different constructor. Thus if we wanted to create instances of our shapes from a dataset we'd have something like the following.

class Program
{
    static void Main(string[] args)
    {
        var data = new[,] { { new[] { 6.0 } }, { new[] { 3.0, 4.0 } }, { new[] { 5.0, 30.0, 7.0 } }, { new[] { 5.0, 3.0 } } };

        foreach (var d in data)
            if (d.Length == 1)
                (new Square(d[0])).Print();
            else if (d.Length == 2)
                (new Rectangle(d[0], d[1])).Print();
            else if (d.Length == 3)
                (new Triangle(d[0], d[1], d[2])).Print();
    }

}

For each data entry we have to check how many values it contains and then create a shape appropriately, now that's sloppy even in this contrived example, so let's define a factory to handle the creation of our shapes.

from our UML you can see that all of our shapes implement the IShape interface, then we have an interface that describes our factory, it defines one function that returns instances of our shapes then that interface is realized by our ShapeFactory which encapsulates our instantiation logic.

interface IShapeFactory
{
    IShape CreateShape(double[] args);
}

Simple as that just one function that takes in our data and returns an instance of IShape; next let's create a concrete implementation of our factory

class ShapeFactoryIShapeFactory
{
    public IShape CreateShape(double[] args)
    {
        if (args.Length == 1)
            return new Square(args[0]);

        if (args.Length == 2)
            return new Rectangle(args[0], args[1]);

        if (args.Length == 3)
            return new Triangle(args[0], args[1], args[2]);

        return null;
    }
}

now we've encapsulated our instantiation logic into a factory and now let's use our factory in our program

class Program
{
    static void Main(string[] args)
    {
        var data = new[,] {
            { new[] { 6.0 } }, { new[] { 3.0, 4.0 } },
            { new[] { 5.0, 30.0, 7.0 } }, { new[] { 5.0, 3.0 } }
        };

        var shapeFactory = new ShapeFactory();
        foreach (var d in data)
            shapeFactory.CreateShape(d).Print();
    }
}

and voila much cleaner code, now that our factory handles the instantiation of our shapes.