Software Design Patterns and Principles - Part 4 (Builder Design Pattern)
Credit: Dev Genius

Software Design Patterns and Principles - Part 4 (Builder Design Pattern)


**** Previous Parts ****

Part 1: Introduction

Part 2: List of Most Used Patterns and Singleton Design Pattern

Part 3: Software Design Patterns and Principles - Part 3 (Factory Method and Abstract Factory Method Design Pattern)


Story:

My friend Bablu starts a house construction company. At the beginning of his company he only offers a small set of features and options for building house to his customer. For example, offers to choose the number of room, number of window, number of washroom and kitchen. Also he creates a software that accept customer choices based on available options that Bablu offers. But day by day his company becomes famous and large. And customer wants more and more options, features and flexibility for building their dream house. Now Bablu offers number of south facing window, door, color of floor, color of ceil etc. and that's why needs to adopt the options and features choice variety in his created software.

Builder Design Pattern:

Definition:

Builder is a creational design pattern that lets you construct complex objects step by step. It is allows you to produce different types and representations of an object using the same construction code.

As the definition stat that, builder pattern helps us building a complex object step by step. Means a object that has a complex creation mechanism and too many options and representations. A object that can be different types and representation based on the creational choice/logic. So in this scenario, builder pattern gives us a central construction coding mechanism that can be used to create different types and representation of the object.

Problem:

In the above story, at the beginning of Babul's construction company, his software has a class named Home. And this class has only the following properties like numberOfRoom, numberOfWindow, numberOfWashroom and numberOfKitchen. So to make this Home class object, class can contain easily a constructor like Home(numberOfRoom, numberOfWindow, numberOfWashroom, numberOfKitchen);

But what about the the next phase where many more properties are being added and every customer don't choose every property. That's why here increasing property and initialize them using constructor is ugly and hard.

So how can we adopt the current and future changes cleanly, easily and can create object by initializing them with proper property values?

Solution:

From the above definition of builder pattern, it gives us a object construction mechanism for different types and representations of same object. So builder pattern is one of the solution of our described problem. What we need is just separate the object construction code and make a interface for the common property initialize/setter methods. Lets called it HouseBuilder. Then create a concrete house builder class called ConcreteHouseBuilder by implement the HouseBuilder interface. We can also create a ConcreteDuplexHouseBuilder class for building duplex house. Then we need to define the product means House class that will be created/building as final result. We also need a Director class which will accepts the customer choice/logic and handle the steps of object initialization/creation.

UML Diagram:

Credit: Google

Here in UML diagram, we see that the Builder interface declares product construction steps that are common for all types of builder. ConcreteBuilder implements the Builder interface and provide implementations of construction steps. Product are the resulting object that are build by ConcreteBuilder. The Director class defines the order of construction steps and associated with Builder. And this Director class are called from a Client class.

When to use:

  1. Use the builder pattern to get rid of the telescopic or giant constructor.
  2. Use the builder pattern when you want you want to create different representations of some product or object.
  3. Use the pattern to build complex object like composite trees.

Implementation:

public class House {
    private final int numberOfRoom;
    private final int numberOfWashroom;
    private final int numberOfWindow;
    private final int numberOfDoorPerRoom;
    private final int numberOfSouthFacingWindow;
    private final int numberOfKitchen;

    public House (int numberOfRoom, int numberOfWashroom, int numberOfWindow, int numberOfDoorPerRoom, int numberOfSouthFacingWindow, int numberOfKitchen) {
        this.numberOfRoom = numberOfRoom;
        this.numberOfWashroom = numberOfWashroom;
        this.numberOfWindow = numberOfWindow;
        this.numberOfDoorPerRoom = numberOfDoorPerRoom;
        this.numberOfSouthFacingWindow = numberOfSouthFacingWindow;
        this.numberOfKitchen = numberOfKitchen;
    }
}

public interface HouseBuilder {
    void setNumberOfRoom(int num);
    void setNumberOfWashroom(int num);
    void setNumberOfWindow(int num);
    void setNumberOfDoorPerRoom(int num);
    void setNumberOfSouthFacingWindow(int num);
    void setNumberOfKitchen(int num);
}

public class ConcreteHouseBuilder implements HouseBuilder {
    private int numberOfRoom;
    private int numberOfWashroom;
    private int numberOfWindow;
    private int numberOfDoorPerRoom;
    private int numberOfSouthFacingWindow;
    private int numberOfKitchen;

    @Override
    public void setNumberOfRoom(int num) {
        this.numberOfRoom = num;
    }

    @Override
    public void setNumberOfWashroom(int num) {
        this.numberOfWashroom = num;
    }

    @Override
    public void setNumberOfWindow(int num) {
        this.numberOfWindow = num;
    }

    @Override
    public void setNumberOfDoorPerRoom(int num) {
        this.numberOfDoorPerRoom = num;
    }

    @Override
    public void setNumberOfSouthFacingWindow(int num) {
        this.numberOfSouthFacingWindow = num;
    }

    @Override
    public void setNumberOfKitchen(int num) {
        this.numberOfKitchen = num;
    }

    public House getHouse() {
        return new House(numberOfRoom, numberOfWashroom, numberOfWindow, numberOfDoorPerRoom, numberOfSouthFacingWindow, numberOfKitchen);
    }
}

public class Director {

    public void constructHouse(HouseBuilder builder) {
        builder.setNumberOfRoom(2);
        builder.setNumberOfWashroom(4);
        builder.setNumberOfWindow(5);
        builder.setNumberOfDoorPerRoom(1);
        builder.setNumberOfSouthFacingWindow(2);
        builder.setNumberOfKitchen(1);
    }
}

public class Demo {

    public static void main(String[] args) {
        Director director = new Director();

        ConcreteHouseBuilder builder = new ConcreteHouseBuilder();
        director.constructHouse(builder);

        Car car = builder.getHouse();
    }
}
        

Another Java implementation of builder design pattern.


Builder pattern has another out of box and easy implementation. Like the following:

public class Computer {
	
	private String HDD;
	private String RAM;
	private boolean isGraphicsCardEnabled;
	private boolean isBluetoothEnabled;
	

	public String getHDD() {
		return HDD;
	}

	public String getRAM() {
		return RAM;
	}

	public boolean isGraphicsCardEnabled() {
		return isGraphicsCardEnabled;
	}

	public boolean isBluetoothEnabled() {
		return isBluetoothEnabled;
	}
	
	private Computer(ComputerBuilder builder) {
		this.HDD=builder.HDD;
		this.RAM=builder.RAM;
		this.isGraphicsCardEnabled=builder.isGraphicsCardEnabled;
		this.isBluetoothEnabled=builder.isBluetoothEnabled;
	}
	
	//Builder Class
	public static class ComputerBuilder{

		private String HDD;
		private String RAM;
		private boolean isGraphicsCardEnabled;
		private boolean isBluetoothEnabled;
		
		public ComputerBuilder(String hdd, String ram){
			this.HDD=hdd;
			this.RAM=ram;
		}

		public ComputerBuilder setGraphicsCardEnabled(boolean isGraphicsCardEnabled) {
			this.isGraphicsCardEnabled = isGraphicsCardEnabled;
			return this;
		}

		public ComputerBuilder setBluetoothEnabled(boolean isBluetoothEnabled) {
			this.isBluetoothEnabled = isBluetoothEnabled;
			return this;
		}
		
		public Computer build(){
			return new Computer(this);
		}
	}
}

public class TestBuilderPattern {

	public static void main(String[] args) {
		Computer comp = new Computer.ComputerBuilder(
				"500 GB", "2 GB").setBluetoothEnabled(true)
				.setGraphicsCardEnabled(true).build();
	}

}        

For small and single variation object, it can be feel as over engineering but for big and with lots of variation of a object builder pattern is a very handy tool.


Achieved Design Principles:

Single Responsibility Principle (SRP): We can achieve SRP by these design patterns. Because different ConcreteBuilder class can contain owns responsibility.

Open Closed Principle (OCP): We can achieve OCP by these design patterns. Because we can extend and create new ConcreteBuilder class for adding new representations without modifying the existing ones.

Interface Segregation (IS): We can achieve IS by these design patterns. Because Builder interface and different ConcreteBuilder class can depends on different interfaces.


Happy Learning !!!

Happy Coding !!!

Happy Programming !!!

Saiful Islam Rasel

Senior Engineer, SDE @ bKash | Ex: AsthaIT | Sports Programmer | Problem Solver | FinTech | Microservice | Java | Spring-boot | C# | .NET | PostgreSQL | DynamoDB | JavaScript | TypeScript | React.js | Next.js | Angular

8 个月
回复

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

社区洞察