Builder, Strategy Patterns Still relevant?

If you agree with me that writing less code equals lesser bugs, you are going to appreciate how Scala simplifies Strategy and Builder design patterns by making you write less code!

Let's look at them one by one. Let us see how the Builder design pattern is implemented in Java.

Builder Pattern

Builder design pattern helps in constructing a complex object in a series of steps. Let me take a classical example of preparing a Pizza, especially a vegetable Pizza! Our Pizza modelled as a POJO would look like below! Assume that we have the needed getters and setters to instantiate a Pizza.

 1public class VegetarianPizza extends Pizza {
 2    private String dough;
 3    private Enum sauceType;
 4    private Enum cheeseType;
 5    private String toppings;
 6    private Enum sizeType;
 7    private Boolean isTomatoAdded;
 8    private Boolean isGarlicAdded;
 9    private Boolean isOnionAdded;
10    private Boolean isSpinachAdded;
11}

Now as I see the instance fields, I can see 9*9 ways of instantiating a VegetarianPizza. I do not definitely want to write those many constructors! Builder pattern to the rescue!

Here is how I could add a VegetarianPizza builder to make a VegetarianPizza instantiation much organised:

 1public class VegetarianPizza extends Pizza {
 2    private String dough;
 3    private Enum sauceType;
 4    private Enum cheeseType;
 5    private String toppings;
 6    private Enum sizeType;
 7    private Boolean isTomatoAdded;
 8    private Boolean isGarlicAdded;
 9    private Boolean isOnionAdded;
10    private Boolean isSpinachAdded;
11
12   //Private constructor to enforce instantiation via Builder
13   private VegetarianPizza(...) { ... }
14   public static class VegetarianPizzaBuilder {
15       private String dough;
16       private Enum sauceType;
17       private Enum cheeseType;
18       private String toppings;
19       private Enum sizeType;
20       private Boolean isTomatoAdded;
21       private Boolean isGarlicAdded;
22       private Boolean isOnionAdded;
23       private Boolean isSpinachAdded;
24       
25       public VegetarianPizzaBuilder withDough(String dough) { 
26           this.dough = dough; return this; 
27       }
28
29       public VegetarianPizzaBuilder withSauceType(Enum sauceType) { 
30           this.sauceType = sauceType; return this; 
31       }
32
33       public VegetarianPizzaBuilder withcheeseType(Enum cheeseType) { 
34           this.cheeseType = cheeseType; return this; 
35       }
36
37       public VegetarianPizzaBuilder withTopping(String toppings) { 
38           this.toppings = toppings; return this; 
39       }
40
41       public VegetarianPizzaBuilder withSize(Enum sizeType) { 
42           this.sizeType = sizeType; return this; 
43       }
44
45       public VegetarianPizzaBuilder withTomato(Boolean isTomatoAdded) { 
46           this.isTomatoAdded = isTomatoAdded; return this; 
47       }
48
49       public VegetarianPizzaBuilder withGarlic(Boolean isGarlicAdded) { 
50           this.isGarlicAdded = isGarlicAdded; return this; 
51       }
52
53       public VegetarianPizzaBuilder withOnion(Boolean isOnionAdded) { 
54           this.isOnionAdded = isOnionAdded; return this; 
55       }
56
57       public VegetarianPizzaBuilder withSpinach(Boolean isSpinachAdded) { 
58           this.isSpinachAdded = isSpinachAdded; return this; 
59       }
60
61       public Pizza buildVegetarianPizza() {
62           return new VegetarianPizza(this);
63       }
64    }
65}

That was roughly 60+ lines of boiler plate code. Ain't that shitty!

Enter Scala, using case classes with default parameters, our VegetarianPizza in Scala would look like

 1case class VegetarianPizza(dough: String = "WhiteDough",
 2  sauceType: SauceType = SauceType.withName("Ketchup"),
 3  cheeseType: CheeseType = CheeseType.withName("Irish"),
 4  toppings: String = "Anything",
 5  sizeType: SizeType = SizeType.withName("Large"),
 6  isTomato: Boolean = true,
 7  isGarlic: Boolean = true,
 8  isOnion: Boolean = true,
 9  isSpinach: Boolean = true
10)

That's all it. Our builder is reduced to less than 10 lines! You can instantiate it like this!

1val defaultVegPizza = VegetarianPizza()
2val withoutOnions = VegetarianPizza(isOnion = false)
3val withoutSpinachAndTomato = VegetarianPizza(isTomato = false, isSpinach = false)

Strategy Pattern

Let's now dive into the Strategy pattern and see what Scala has to offer in terms of brevity The definition of the strategy pattern goes like this "... enable an algorithm's behavior to be determined at runtime. Let me take the classical example of doing something with two Integers. What could be done with them? Add, Subtract, Multiply or Divide! Let's dive into some code. Let's first do it the ugly way using Java:

1interface Strategy {
2    int execute(int a, int b);
3}

Our concrete implementations for Add, Subtract, Multiply and Divide

 1class Add implements Strategy {
 2    public int execute(int a, int b) {
 3        return a + b;
 4    }
 5}
 6
 7class Subtract implements Strategy {
 8    public int execute(int a, int b) {
 9        return a - b;
10    }
11}
12
13class Multiply implements Strategy {
14    public int execute(int a, int b) {
15        return a * b;
16    }
17}
18
19class Divide implements Strategy {
20    public int execute(int a, int b) {
21        return a / b;
22    }
23}

We now wrap our strategy in a context object, so that the clients can call our strategy depending on the context of what they want to do with the strategy.

 1class Context {
 2    private Strategy strategy;
 3
 4    public Context(Strategy strategy) {
 5        this.strategy = strategy;
 6    }
 7
 8    public int executeStrategy(int a, int b) {
 9        return this.strategy.execute(a, b);
10    }
11}

All our client or the caller of our strategy needs to do is to get a context, pass in the strategy to the context and execute the strategy within that context.

Here it is in code!

1    Context ctx = new Context(new Add()); 
2    ctx.executeStrategy(1, 2);

All good so far. If you watch closely, it is all about invoking the algorithm that does the actual job. What is an algorithm? In our example above it is just our Add, Subtract, Multiply and Divide functions. What? Did I just say functions .... functional programming.... Scala!

Let's now see how Scala addresses this with a functional approach

1object IWillNotWriteBoilerPlateStrategyPatternAnymore extends App {
2  def add(a: Int, b: Int) = a + b
3  def sub(a: Int, b: Int) = a - b
4  def mul(a: Int, b: Int) = a * b
5  def div(a: Int, b: Int) = a / b
6
7  def execute(fn: (Int, Int) => Int, a: Int, b: Int) = fn(a, b)
8}

That is all it! This is what we call simplicity! Here is how to call it!

1execute(add, 1, 2)
2execute(mul, 1, 2)

So I hope that I was able to convince you with some historical design patterns and how you could simplify them with a functional programming approach. Now, since Java 8 has functional paradigm baked into the language, I still find Scala's functional approach is lot simpler and subtler! The choice is your's!