Design Pattern: Builder Pattern

What is it?

According to Gang of Four, “Separates the construction of a complex object from its representation so that the same construction process can create different representations.” Again, no idea reading it for the first time 🙂

So, stated simply, we try to separate the construction of a complex object from its representation to reuse the building process for creating different representations.

Let’s take an example of subway, where we provide all the details of vegetables and sauces needed (data) and we get the sandwich (complex object). Here, we did not tell how to prepare it (building process). This is what we want to do with object creation process too.

In software world:

To create a sandwich object, you pass all the parameters in the constructor to create the object. This can get worse if we have multiple constructors with different parameters.

Improvement #1:

Remove the parameters from the constructor and set all the properties you need. Something like:

 Sandwich sandwich = new Sandwich();
 sandwich.setBreadType(BreadType.WHITE);
 sandwich.setHasMayo(true); ...

But, now we need to check if we have set all the properties. We have no way to set the order. I mean, you need to provide the bread type before vegetables and sauce you need that can not be controlled anymore. So, we need a builder that takes all details and build it using proper steps.

Improvement #2:

class SandwichBuilder{
       Sandwich sandwich;

       public Sandwich getSandwich(){
             return sandwich; 
       }

       public void createSandwich(){
              sandwich = new Sandwich();
              sandwich.setBreadType(BreadType.WHITE);
              .... 
       }
}

It can be further modified to modularize and make the building process more meaningful with functions doing smaller tasks being called from createSandwich

       public void createSandwich(){
              sandwich = new Sandwich();
              selectBreadType();
              addVegetables();
              addSauces();
              .... 
       }

So, now we have a builder whose object can be used to create a new sandwich having same properties using this builder.

Also, this helps when creation of the complex object (sandwich here) changes i.e. we can change the code inside the build function (createSandwich here).

One more advantage here is if we have more than one kind of complex object (different types of sandwich), just create a new builder class to handle that and we can reuse the creation of the complex object de-coupling it from its representation.

But, now we need to create multiple builder classes with similar functions without any relation between them (no common interface implemented by them) and much of code will be replicated in these builders.

Improvement #3

Create an abstract class controlling the structure of the builder which can be used by each builder to use the same procedure with different values (ingredients here):

abstract class SandwichBuilder{
        Sandwich sandwich;

        public Sandwich getSandwich(){
               return sandwich;
        }

        public void createNewSandwich(){
               sandwich=new Sandwich();
        }

        public abstract void addVegetables();
        public abstract void addSauces();
        ....
}

So, this class here now encapsulates the commonalities between the builders but who handles the procedure of building the sandwich.

It should not be added here as this is the class that does the work common to all the builders. Here is what we do to mitigate this:

class SandwichMaker{
      private SandwichBuilder builder;

      public SandwichMaker(SandwichBuilder builder){
             this.builder = builder;
      }

      public void buildSandwich(){
             builder.createNewSandwich();
             builder.selectBread();
             builder.addVegetables();
             builder.addSauces();
      }

      public Sandwich getSandwich(){
             return builder.getSandwich();
      }
}

So, now our builder class just handles the properties (ingredients here) to create the complex object and the creation steps are handled by the maker and your code using this will look something like this:

      SandwichMaker maker = new SandwichMaker(new MyFavSandwichBuilder());
      maker.buildSandwich();
      Sandwich sandwich = maker.getSandwich();

The Builder Pattern Design:

Director (SandwichMaker) <>——- Builder (SandwichBuilder) <———- ConcreteBuilder (MyFavSandwichBuilder) ——-> Product (Sandwich)

So in Hollywood terms:

Director knows how to build

ConcreteBuilder defines the steps for creation of the Product used by Director

Where to use:

  • Need to create a complex object with lot of details embedded into it.
  • Multiple data combination to create multiple types of products using same steps.

Design Pattern: Bridge Pattern

Definition:

According to Gang of Four, “Decouple an abstraction from its implementation so the two can vary independently”. If you did not understand it, it is pretty normal 🙂 Lets discuss it in detail.

Lets understand the definition:

So for any kind of abstraction, we create an interface and implement that interface in a class. Now, our client code uses this interface to call the implemented code. But, every time the interface changes, we need to change the implemented code. How can we de couple the abstraction and its implementation?

Example:

We have different types of documents that have print functionality in them and we have created a abstract super class GenericDocument that has this print method.

Now lets say we get a requirement to have a Document to have two different types of printing (ex. remove images while printing). One option is to create a new subclass of that Document and implement print accordingly.

  • But what if all documents may need this new type of Print tomorrow? Do we create a new subclass every time?
  • What if we get multiple different print types. Should we create a new sub class for each type of document?

Solution:

To have a generic print that can be used by each type of document if needed, we can create a print helper that can handle each type of print request and this helper can be used by each document’s print method without the need of any new sub class creation for each new print type.

GenericDocument <— Book, Research Paper, Design Document

Create a PrintHelper interface that has two implementations: NormalPrint and PrintWithoutImages implementation.

Now, GenericDocument has PrintHelper passed in the constructor that can be used by each document’s print method solving the whole purpose.

What this pattern did?

Abstraction (GenericDocument) uses Implementor (PrintHelper)

RefinedAbstraction (Book, Research Paper etc.) uses ConcreteImplementor (NormalPrint, PrintWithoutImages)

When to use it?

You have some abstraction (GenericDocument) and some implementations (Book, Research Paper etc.) already existing but you need to add some functionality (Print without images) that can be used by all the implementations, you can go ahead and use bridge pattern.

Design Pattern : Adapter Pattern

What is Adapter Pattern?

Lets say you need to use a library that expects a particular interface and client code (that needs to call the library) currently expects a particular generic interface which is not supported by the library.

What should be done?

Change the client to use the new interface and call the library to solve the purpose. What if few years later, you need to use another library that uses a completely new interface?

Should we change the client code again to suite the new interface?

What does this pattern solve?

  • Allow clients to use other code and work together which otherwise could not use due to incompatible interfaces
  • Lets say you are writing a useful functionality that you think is reusable for variety of users, then you can provide the Adapter support so that clients can implement the adapter to suit there use cases.

Solution

In such cases, adapter patterns come to rescue solving these problems. Lets see how:

Naming convention:

Client: You code that needs to use some functionality exposed by third party code you do not have control upon.

Adaptee: The implementation you need to use

Adapter: The interface that is used by the client to use Adaptee’s operation

ConcreteAdapter: Implements Adapter and calls Adaptee providing the results back to client.

Client —> Adapter (Interface) —> ConcreteAdapter (Implementation) —> Adaptee

Points to consider

  • Apart from the mentioned use case, it can also be used in the implementation to de couple your code from the implementation.
  • For example, you can use it to handle which db your code connects to. Just create one Adapter implementation for each db you want to support.