Sunday, 29 October 2017

Builder Pattern

The builder pattern is used to separate logic from data, it's defined as "Separation of the construction of a complex object from its representation so that the same construction process can be used to create multiple representations." What we are aiming for is to reuse common logic to create objects of the same type but different representations.

For example let's look at making a three part dinner, so to me a three part dinner is composed of a protein, a starch and a salad; i basically eat this for dinner almost everyday, but the components of the dinner vary. one day i might do Salmon, spinach, yams, another day i might do chicken breast, caesar salad and buckwheat. the point is that the dinner itself is the same, but the components of the dinner are different.

To get started we are going to need a Dinner class that store all of the properties of our dinner

enum ProtienStyle {raw = 0, bloody = 10, rare = 30, medium = 60, wellDone = 90 }
enum Protien { Chicken= 1, Turkey=2, Salmon=4, Ostrich=8, Squid=16, Bison=32 }
enum Green { Cabbage = 1, Salad = 2, Spinach = 4, Pickles = 8, Cucumber = 16, Peppers = 32, Carrots = 64 }
enum Starch { Yams, Potatos, Buckweat, Bread }

class Dinner
{
    public ProtienStyle ProtienStyle { get; set; }
    public Protien Protien { get; set; }
    public Green Salad { get; set; }
    public Starch Starch { get; set; }

    public string Display()
    {
        var sb = new StringBuilder();
        string protiens = Enum.GetValues(typeof(Protien)).Cast<Protien>()
            .Where(p => (p & Protien) == p)
            .Select(g => g.ToString())
            .Aggregate((x, y) => $"{x}, {y}");

        sb.AppendLine($"Protien:{protiens} are {ProtienStyle.ToString()}");
        sb.AppendLine($"Starch:{Starch.ToString()}");

        string ingredients = Enum.GetValues(typeof(Green)).Cast<Green>()
            .Where(i => (i & Salad) == i)
            .Select(g => g.ToString())
            .Aggregate((x, y) => $"{x}, {y}");

        sb.AppendLine($"Salad:{ingredients}");

        return sb.ToString();
    }

}

above we define our dinner class, which stores the various components of our dinner next we need to define an abstract dinner builder.

abstract class DinnerBuilder
{
    public Dinner Dinner { get; private set; } = new Dinner();
    public abstract void MakeSalad();
    public abstract void CookStarch();
    public abstract void CookMeat();

}


this class defines the abstraction that our concrete implementations are going to have to define, next let's look at our implementations

class SeafoodDinner : DinnerBuilder
{
    public SeafoodDinner() => Dinner.ProtienStyle = ProtienStyle.raw;
    public override void CookMeat() => Dinner.Protien = Protien.Salmon | Protien.Squid;
    public override void CookStarch() => Dinner.Starch = Starch.Yams;
    public override void MakeSalad() => Dinner.Salad = Green.Cabbage;
}

class SurfAndTurfDinner : DinnerBuilder
{
    public SurfAndTurfDinner() => Dinner.ProtienStyle = ProtienStyle.rare;
    public override void CookMeat() => Dinner.Protien = Protien.Bison | Protien.Squid;
    public override void CookStarch() => Dinner.Starch = Starch.Buckweat;
    public override void MakeSalad() => Dinner.Salad = Green.Salad | Green.Peppers;

}

we can see that this implementations of our Builder class really have no logic, they just hold our data, now our logic is in our DinnerCooker class, this would be referred to as the director.

class DinnerCooker
{
    DinnerBuilder _dinnerBuilder;
    public DinnerCooker(DinnerBuilder dinnerBuilder)
        => _dinnerBuilder = dinnerBuilder;

    public void CookDinner()
    {
        _dinnerBuilder.CookStarch();
        _dinnerBuilder.CookMeat();
        _dinnerBuilder.MakeSalad();
    }

    public Dinner GetDinner() => _dinnerBuilder.Dinner;

}

in our data cooker, is where the logic sits; the order you'd create your dinner, you'd start with your starch because that takes the longest, then you'd start your meat, and then while those two where cooking you'd make your salad.

now let's take a look at our main

class Program
{
    static void Main(string[] args)
    {
        var dc1 = new DinnerCooker(new SeafoodDinner());
        dc1.CookDinner();
        Console.WriteLine(dc1.GetDinner().Display());

        var dc2 = new DinnerCooker(new SurfAndTurfDinner());
        dc2.CookDinner();
        Console.WriteLine(dc2.GetDinner().Display());
    }

}

and as you can see we create our dinnerCooker and pass in our builder to it; the cooker then uses the data in the builder to create our dinner.

using System;
using System.Linq;
using System.Text;

namespace pc.patternBuilder
{
    enum ProtienStyle { raw = 0, bloody = 10, rare = 30, medium = 60, wellDone = 90 }
    enum Protien { Chicken = 1, Turkey = 2, Salmon = 4, Ostrich = 8, Squid = 16, Bison = 32 }
    enum Green { Cabbage = 1, Salad = 2, Spinach = 4, Pickles = 8, Cucumber = 16, Peppers = 32, Carrots = 64 }
    enum Starch { Yams, Potatos, Buckweat, Bread }
    class Dinner
    {
        public ProtienStyle ProtienStyle { get; set; }
        public Protien Protien { get; set; }
        public Green Salad { get; set; }
        public Starch Starch { get; set; }

        public string Display()
        {
            var sb = new StringBuilder();
            string protiens = Enum.GetValues(typeof(Protien)).Cast<Protien>()
                .Where(p => (p & Protien) == p)
                .Select(g => g.ToString())
                .Aggregate((x, y) => $"{x}, {y}");

            sb.AppendLine($"Protien:{protiens} are {ProtienStyle.ToString()}");
            sb.AppendLine($"Starch:{Starch.ToString()}");

            string ingredients = Enum.GetValues(typeof(Green)).Cast<Green>()
                .Where(i => (i & Salad) == i)
                .Select(g => g.ToString())
                .Aggregate((x, y) => $"{x}, {y}");

            sb.AppendLine($"Salad:{ingredients}");

            return sb.ToString();
        }
    }

    abstract class DinnerBuilder
    {
        public Dinner Dinner { get; private set; } = new Dinner();
        public abstract void MakeSalad();
        public abstract void CookStarch();
        public abstract void CookMeet();
    }

    class SeafoodDinner : DinnerBuilder
    {
        public SeafoodDinner() => Dinner.ProtienStyle = ProtienStyle.raw;
        public override void CookMeet()
            => Dinner.Protien = Protien.Salmon | Protien.Squid;
        public override void CookStarch()
            => Dinner.Starch = Starch.Yams;
        public override void MakeSalad()
            => Dinner.Salad = Green.Cabbage;
    }

    class SurfAndTurfDinner : DinnerBuilder
    {
        public SurfAndTurfDinner() => Dinner.ProtienStyle = ProtienStyle.rare;
        public override void CookMeet()
            => Dinner.Protien = Protien.Bison | Protien.Squid;
        public override void CookStarch()
            => Dinner.Starch = Starch.Buckweat;
        public override void MakeSalad()
            => Dinner.Salad = Green.Salad | Green.Cucumber | Green.Peppers;
    }

    class DinnerCooker
    {
        DinnerBuilder _dinnerBuilder;
        public DinnerCooker(DinnerBuilder dinnerBuilder)
            => _dinnerBuilder = dinnerBuilder;

        public void CookDinner()
        {
            _dinnerBuilder.CookMeet();
            _dinnerBuilder.CookStarch();
            _dinnerBuilder.MakeSalad();
        }

        public Dinner GetDinner() => _dinnerBuilder.Dinner;
    }


    class Program
    {
        static void Main(string[] args)
        {
            var dc1 = new DinnerCooker(new SeafoodDinner());
            dc1.CookDinner();
            Console.WriteLine(dc1.GetDinner().Display());

            var dc2 = new DinnerCooker(new SurfAndTurfDinner());
            dc2.CookDinner();
            Console.WriteLine(dc2.GetDinner().Display());
        }
    }
}