Builder Pattern
@cravethebenefits

Builder Pattern

Builder pattern is one of the creational design patterns. It is used to delegate the creation of complex objects. In this case complex objects encompasses objects with different possible representations.

Motivation

When creating a complex object, the code has to be readable. In a situation where there are different representations of an object, the class will have multiple properties some can be optional, These properties define a constructor and constructors of this type of class can be overloaded with different arguments depending on the representation of the object needed. This problem is called Telescoping Constructor Anti-pattern.

In this article, a smoothie will be created using telescoping constructors patterns and builder pattern, There are two representations of the smoothie; pineapple-ginger smoothie and mango-banana-strawberry smoothie.

public class Smoothie {

   private String milk;

   private String pineapple;

   private String ginger;

   private String banana;

   private String mango;

   private String strawberry;

   public Smoothie(String milk, String pineapple, String ginger, String banana, String mango, String strawberry) {

    

       this.milk = milk;

       this.pineapple = pineapple;

       this.ginger = ginger;

       this.banana = banana;

       this.mango = mango;

       this.strawberry = strawberry;

   }

}
        

Instantiating the Smoothie class, all the properties listed in the class are required. Now imagine building a pineapple-ginger smoothie, the required properties are milk, pineapple, and ginger. It can be noted that we have provided a lot of properties that are not needed for pineapple-ginger which is quite unnecessary and a waste of resources. The same procedure goes for building mango-banana-strawberry smoothie, we instantiate the Smoothie class with properties like pineapple and ginger that are not required to build the object flavour.

In the code block below, we are attempting to use different constructors passing different arguments based on the smoothie we need to build. The problem with this pattern is that it becomes difficult to remember the required constructor in a particular situation or the parameter order which reduces the readability of the code.

public Smoothies(String milk, String pineapple, String ginger) {

   this.milk = milk;

   this.pineapple = pineapple;

   this.ginger = ginger;

}        


public Smoothies(String milk, String banana, String mango, String strawberry) {

   this.milk = milk;

   this.banana = banana;

   this.mango = mango;

   this.strawberry = strawberry;

}        


With builder pattern, we can reduce the number of overloading constructors while constructing different representations of objects.

package builder;

public class Smoothie {

   private final String milk;

   private final String pineApple;

   private final String banana;

   private final String mango;

   private final String ginger;

   private final String strawberry;

   public Smoothie(Builder builder) {

       milk = builder.milk;

       pineApple = builder.pineApple;

       banana = builder.banana;

       mango = builder.mango;

       ginger = builder.ginger;

       strawberry = builder.strawberry;

   }

   @Override

   public String toString() {

       StringBuilder sb = new StringBuilder();

       sb.append("Smoothie is ready and the ingredients are:").append(" ");

       sb.append(milk).append(" ");

       if (pineApple != null) sb.append(pineApple).append(" ");

       if (banana != null) sb.append(banana).append(" ");

       if (mango != null) sb.append(mango).append(" ");

       if (ginger != null) sb.append(ginger).append(" ");

       if (strawberry != null) sb.append(strawberry).append(" ");

       return sb.toString();

   }

   public static class Builder {

       private String milk;

       private String pineApple;

       private String banana;

       private String mango;

       private String ginger;

       private String strawberry;

       public Builder(String milk) {

           if (milk == null) {

               throw new IllegalArgumentException("milk can not be null");

           }

           this.milk = milk;

       }

       public Builder withPineApple(String pineApple) {

           this.pineApple = pineApple;

           return this;

       }

       public Builder withBanana(String banana) {

           this.banana = banana;

           return this;

       }

       public Builder withMango(String mango) {

           this.mango = mango;

           return this;

       }

       public Builder withGinger(String ginger) {

           this.ginger = ginger;

           return this;

       }

       public Builder withStrawberry(String strawberry) {

           this.strawberry = strawberry;

           return this;

       }

       public Smoothie build() {

           return new Smoothie(this);

       }

   }

}
        

Here, Smoothie object makes use of the builder class to build ingredients for the smoothie by calling the methods that initialize the required properties as needed. To create a pineapple-ginger smoothie, we will call the builder methods withGinger and withPineApple and then we call the build method with returns a Smoothie object with milk , pineapple and ginger.

Smoothie pineAppleGingerSmoothie =

       new Smoothie.Builder("Milk")

       .withPineApple("Pineapple")

       .withGinger("Ginger")

       .build();        

To create a mango-banana-strawberry smoothie, we will call the builder methods withMango , withBanana and withStrawBerry and then we call the build method with returns a Smoothie object with milk , mango, banana and strawberry.

Smoothie bananaMangoStrawBerrySmoothie =

       new Smoothie.Builder("Milk")

               .withBanana("Banana")

               .withMango("Mango")

               .withStrawberry("Strawberry")

               .build();        

When we initialize this object, we print the toString method to know the properties that are needed based on each representation of the object or smoothie flavour in this case.

System.out.println(pineAppleGingerSmoothie.toString());

System.out.println(bananaMangoStrawBerrySmoothie.toString());        
Smoothie is ready and the ingredients are: Milk Pineapple Ginger

Smoothie is ready and the ingredients are: Milk Banana Mango Strawberry        

Advantages

1. Builder pattern allows easier creation of objects that can have different flavors using a standard protocol.

2. You can isolate complex constructions from the business logic which ensures single responsibility principle is followed.

3. Builder pattern makes it easier to add a new flavour/representation without breaking existing changes.

4. It makes your code clear and readable.

Disadvantage

It is verbose.

Use cases

UI libraries where complex objects need to be created.

Real-Life Examples

1. OkHttp

2. StringBuilder

Conclusion

Builder pattern provides an elegant solution that helps to create complex objects clearly and organised. Click on the link to check the full implementation.

Shout out to @cravethebenefits for the photos

要查看或添加评论,请登录

Iyanu Falaye的更多文章

  • Design Patterns.

    Design Patterns.

    Design patterns are a collection of standard solutions to solve software development design problems. Software…

社区洞察

其他会员也浏览了