Blog Archives
Builder Pattern
Who does not know the StringBuilder in namespace System.Text?
The purpose of the StringBuilder is to construct a string much faster than using the operator overload “+”. Strings are immutable. Each time you use the overloaded operator, a new object is created. That slows down the creation of the final object. Here is a quick benchmark:
using System; using System.Diagnostics; using System.Text; namespace demo { class Program { static void Main(string[] args) { Stopwatch lStopwatch = new Stopwatch(); lStopwatch.Start(); string s = ""; for (int i = 0; i < 100000; i++) s += "x"; lStopwatch.Stop(); Console.WriteLine("Operator, elapsed ms: " + lStopwatch.ElapsedMilliseconds); StringBuilder lStringBuilder = new StringBuilder(); lStopwatch.Restart(); for (int i = 0; i < 100000; i++) lStringBuilder.Append("x"); lStopwatch.Stop(); Console.WriteLine("StringBuilder, elapsed ms: " + lStopwatch.ElapsedMilliseconds); Console.ReadLine(); } // } // class } // namespace
example output:
Operator, elapsed ms: 3029
StringBuilder, elapsed ms: 1
The StringBuilder supports method chaining. You can shorten your source code and write:
string s = new StringBuilder() .Append("a") .Append("b") .Append("c") .Append("d") .ToString();
instead of
StringBuilder lStringBuilder = new StringBuilder(); lStringBuilder.Append("a"); lStringBuilder.Append("b"); lStringBuilder.Append("c"); lStringBuilder.Append("d"); string s = lStringBuilder.ToString();
In general the purpose of a Builder is to separate complex object construction from its representation.
We leave the idea of strings behind and build something entirely different. This time it is not about speed, it is more about avoiding thousands of parameters in constructors and making complexity look a little bit easier. Two weeks ago I was using BMW AG to give an example for an Abstract Factory. Let’s stay loyal to cars and build classes for the production assembly.
using System; namespace demo { [Flags] public enum eRadio { GPS = 1, MP3 = 2, Screen = 4, Phone = 8 } public enum eColor { NotSet = 0, Silver, Red, Blue, White, Black } class Program { public class Car { public readonly eRadio Radio; public readonly eColor Color; public readonly double Displacement; public readonly int Doors; public readonly bool Hatchback; public readonly bool LeatherSeats; internal Car(eRadio xRadio, eColor xColor, double xDisplacement, int xDoors, bool xHatchback, bool xLeatherSeats) { Radio = xRadio; Color = xColor; Displacement = xDisplacement; Doors = xDoors; Hatchback = xHatchback; LeatherSeats = xLeatherSeats; } // constructor } // class public class CarBuilder { private eRadio _Radio = 0; private eColor _Color = eColor.NotSet; private double _Displacement = 0.0; private uint _Doors = 1; private bool? _Hatchback = null; private bool? _LeatherSeats = null; internal CarBuilder SetColor(eColor xValue) { _Color = xValue; return this; } internal CarBuilder SetDisplacement(double xValue) { _Displacement = xValue; return this; } internal CarBuilder SetDoors(uint xValue) { _Doors = xValue; return this; } internal CarBuilder SetHatchback(bool xValue) { _Hatchback = xValue; return this; } internal CarBuilder SetRadio(eRadio xValue) { _Radio = xValue; return this; } internal CarBuilder SetLeatherSeats(bool xValue) { _LeatherSeats = xValue; return this; } internal Car Create() { if (_Hatchback == null) throw new Exception("Hatchback not set"); if (_LeatherSeats == null) throw new Exception("LeatherSeats not set"); if (_Doors < 1 || _Doors > 6) throw new Exception("number of Doors invalid"); // ... etc. return new Car(_Radio, _Color, _Displacement, (int)_Doors, (bool)_Hatchback, (bool)_LeatherSeats); } } // class static void Main(string[] args) { Car lCar = new CarBuilder() .SetColor(eColor.Silver) .SetDisplacement(2.2) .SetDoors(5) .SetHatchback(true) .SetRadio(eRadio.GPS | eRadio.MP3 | eRadio.Screen) .SetLeatherSeats(true) .Create(); Console.ReadLine(); } // main } // class } // namespace
Cascade pattern / Method chaining
Today, I am going to explain a simple programming pattern step by step.
Often the cascade pattern can be found in connection with consecutive data manipulation. It has many names. In C# I would put it in the same chapter as Extension Methods.
But let us start at the very beginning. Genesis 🙂
This is a simple integer calculation.
int r0 = 5; r0 = ((((r0 + 2) * 4) - 8) / 2) + 1; Console.WriteLine("Result is " + r0); // 11
We could break it down into many separate calculation steps …
int r1 = 5; r1 += 2; r1 *= 4; r1 -= 8; r1 /= 2; r1 += 1; Console.WriteLine("Result is " + r1); // 11
… and build a class to perform reoccurring calculations.
public class Classic { static public int add(int a, int b) { return a + b; } static public int sub(int a, int b) { return a - b; } static public int mul(int a, int b) { return a * b; } static public int div(int a, int b) { return a / b; } } // class int r2 = 5; r2 = Classic.add(r2, 2); r2 = Classic.mul(r2, 4); r2 = Classic.sub(r2, 8); r2 = Classic.div(r2, 2); r2 = Classic.add(r2, 1); Console.WriteLine("Result is " + r2); // 11
Well, this is nice. But the code is clumsy. We could overload operators. Anyhow, this is not the way I would like to go today. What about a memory variable in form of a property called “Result”.
public class Cascade1 { public int Result { get; private set; } public Cascade1(int x) { Result = x; } public Cascade1 add(int x) { Result += x; return this; } public Cascade1 sub(int x) { Result -= x; return this; } public Cascade1 mul(int x) { Result *= x; return this; } public Cascade1 div(int x) { Result /= x; return this; } } // class int c1 = new Cascade1(5) .add(2) .mul(4) .sub(8) .div(2) .add(1) .Result; Console.WriteLine("Result is " + c1); // 11
This looks much better, doesn’t it?
We replace the integers by a simple class, which only has one public field called “Age”.
public class Data { public int Age; } // class
We add IENumberable as parameter. Now, the program looks neat and more flexible.
public class Cascade2 { public static void add(IEnumerable<Data> xIENumberable, int xValue) { foreach (Data x in xIENumberable) x.Age += xValue; } public static void sub(IEnumerable<Data> xIENumberable, int xValue) { foreach (Data x in xIENumberable) x.Age -= xValue; } public static void mul(IEnumerable<Data> xIENumberable, int xValue) { foreach (Data x in xIENumberable) x.Age *= xValue; } public static void div(IEnumerable<Data> xIENumberable, int xValue) { foreach (Data x in xIENumberable) x.Age /= xValue; } } // class List<Data> lList = new List<Data>(); for (int i = 0; i < 10; i++) lList.Add(new Data() { Age = i }); Cascade2.add(lList, 2); Cascade2.mul(lList, 4); Cascade2.sub(lList, 8); Cascade2.div(lList, 2); Cascade2.add(lList, 1); Console.Write("Array result is "); lList.ForEach(x => Console.Write(x.Age + " "));
But once again we face too many parameters. Should we add a memory field? No, C# offers Extension Methods. This is the way to go!
public static class Cascade3 { public static IEnumerable<Data> add(this IEnumerable<Data> xIENumberable, int xValue) { foreach (Data x in xIENumberable) x.Age += xValue; return xIENumberable; } // public static IEnumerable<Data> sub(this IEnumerable<Data> xIENumberable, int xValue) { foreach (Data x in xIENumberable) x.Age -= xValue; return xIENumberable; } // public static IEnumerable<Data> mul(this IEnumerable<Data> xIENumberable, int xValue) { foreach (Data x in xIENumberable) x.Age *= xValue; return xIENumberable; } // public static IEnumerable<Data> div(this IEnumerable<Data> xIENumberable, int xValue) { foreach (Data x in xIENumberable) x.Age /= xValue; return xIENumberable; } // } // class List<Data> lList = new List<Data>(); for (int i = 0; i < 10; i++) lList.Add(new Data() { Age = i }); lList.add(2) .mul(4) .sub(8) .div(2) .add(1); Console.Write("Array result is "); lList.ForEach(x => Console.Write(x.Age + " "));
Each List object can now use the extension methods. In C# you most likely have come across such pattern when using the namespace System.Linq. Adding this namespace automatically enables many methods for lists (IENumerable) and arrays. Here is an example of that namespace. You can chain together methods. Make sure the return parameter points to the same list each time, otherwise the chain is broken. For instance a Sum() would return a number rather an IENumerable, thus breaking the chain.
List<Data> lList = new List<Data>(); for (int i = 0; i < 10; i++) lList.Add(new Data() { Age = i }); // using System.Linq; List<Data> lResult = lList.Where(x => x.Age % 2 == 0).Take(10).ToList(); Console.Write("Array result is "); lResult.ForEach(x => Console.Write(x.Age + " "));
And here is the entire source code in one piece.
using System; using System.Collections.Generic; using System.Linq; namespace CascadePattern { public class Data { public int Age; } // class public static class Cascade3 { public static IEnumerable<Data> add(this IEnumerable<Data> xIENumberable, int xValue) { foreach (Data x in xIENumberable) x.Age += xValue; return xIENumberable; } // public static IEnumerable<Data> sub(this IEnumerable<Data> xIENumberable, int xValue) { foreach (Data x in xIENumberable) x.Age -= xValue; return xIENumberable; } // public static IEnumerable<Data> mul(this IEnumerable<Data> xIENumberable, int xValue) { foreach (Data x in xIENumberable) x.Age *= xValue; return xIENumberable; } // public static IEnumerable<Data> div(this IEnumerable<Data> xIENumberable, int xValue) { foreach (Data x in xIENumberable) x.Age /= xValue; return xIENumberable; } // } // class class Program { public class Classic { static public int add(int a, int b) { return a + b; } static public int sub(int a, int b) { return a - b; } static public int mul(int a, int b) { return a * b; } static public int div(int a, int b) { return a / b; } } // class public class Cascade1 { public int Result { get; private set; } public Cascade1(int x) { Result = x; } public Cascade1 add(int x) { Result += x; return this; } public Cascade1 sub(int x) { Result -= x; return this; } public Cascade1 mul(int x) { Result *= x; return this; } public Cascade1 div(int x) { Result /= x; return this; } } // class public class Cascade2 { public static void add(IEnumerable<Data> xIENumberable, int xValue) { foreach (Data x in xIENumberable) x.Age += xValue; } // public static void sub(IEnumerable<Data> xIENumberable, int xValue) { foreach (Data x in xIENumberable) x.Age -= xValue; } // public static void mul(IEnumerable<Data> xIENumberable, int xValue) { foreach (Data x in xIENumberable) x.Age *= xValue; } // public static void div(IEnumerable<Data> xIENumberable, int xValue) { foreach (Data x in xIENumberable) x.Age /= xValue; } // } // class static void Main(string[] args) { int r0 = 5; r0 = ((((r0 + 2) * 4) - 8) / 2) + 1; Console.WriteLine("Result is " + r0); // 11 int r1 = 5; r1 += 2; r1 *= 4; r1 -= 8; r1 /= 2; r1 += 1; Console.WriteLine("Result is " + r1); // 11 int r2 = 5; r2 = Classic.add(r2, 2); r2 = Classic.mul(r2, 4); r2 = Classic.sub(r2, 8); r2 = Classic.div(r2, 2); r2 = Classic.add(r2, 1); Console.WriteLine("Result is " + r2); // 11 int c1 = new Cascade1(5) .add(2) .mul(4) .sub(8) .div(2) .add(1) .Result; Console.WriteLine("Result is " + c1); // 11 List<Data> lList = new List<Data>(); for (int i = 0; i < 10; i++) lList.Add(new Data() { Age = i }); Cascade2.add(lList, 2); Cascade2.mul(lList, 4); Cascade2.sub(lList, 8); Cascade2.div(lList, 2); Cascade2.add(lList, 1); Console.Write("Array result is "); lList.ForEach(x => Console.Write(x.Age + " ")); Console.WriteLine(); lList.Clear(); for (int i = 0; i < 10; i++) lList.Add(new Data() { Age = i }); lList.add(2) .mul(4) .sub(8) .div(2) .add(1); Console.Write("Array result is "); lList.ForEach(x => Console.Write(x.Age + " ")); Console.WriteLine(); // using System.Linq; lList.Clear(); for (int i = 0; i < 50; i++) lList.Add(new Data() { Age = i }); List<Data> lResult = lList.Where(x => x.Age % 2 == 0).Take(10).ToList(); Console.Write("Array result is "); lResult.ForEach(x => Console.Write(x.Age + " ")); Console.ReadLine(); } // } // class } // namespace
example output:
Result is 11
Result is 11
Result is 11
Result is 11
Array result is 1 3 5 7 9 11 13 15 17 19
Array result is 1 3 5 7 9 11 13 15 17 19
Array result is 0 2 4 6 8 10 12 14 16 18