Builder Pattern
Photo by Marcel Strau? on Unsplash

Builder Pattern

The What

The Builder Pattern is a creational design pattern used to construct complex objects step by step. This pattern allows you to produce different types and representations of an object using the same construction code. Essentially, the Builder Pattern separates the construction of a complex object from its representation, enabling you to create different types and representations of an object using the same construction process.

The General Problem It Tries to Solve

When constructing complex objects, such as those with many parameters or nested structures, code can become unwieldy and difficult to manage. Traditional approaches like telescoping constructors (constructors with many parameters) can make the code hard to read and maintain. This is especially true when not all parameters are always needed, leading to constructors with many optional parameters.

Example of the General Problem

Consider a scenario where we need to create different types of computers. A computer may have many optional features like a graphics card, sound card, and additional peripherals. Using traditional constructors, you might end up with something like this:

class Computer {
    private final String CPU;
    private final String RAM;
    private final int storage;
    private final boolean hasGraphicsCard;
    private final boolean hasSoundCard;

    public Computer(String CPU, String RAM, int storage) {
        this(CPU, RAM, storage, false, false);
    }

    public Computer(String CPU, String RAM, int storage, 
          boolean hasGraphicsCard) {
        this(CPU, RAM, storage, hasGraphicsCard, false);
    }

    public Computer(String CPU, String RAM, int storage,
          boolean hasGraphicsCard, boolean hasSoundCard) {
        this.CPU = CPU;
        this.RAM = RAM;
        this.storage = storage;
        this.hasGraphicsCard = hasGraphicsCard;
        this.hasSoundCard = hasSoundCard;
    }

    // Getters and other methods...
}        

This approach is cumbersome and does not scale well as the number of optional parameters increases.

Generalizing the When

The Builder Pattern is useful when:

  1. You need to construct an object with many optional parameters.
  2. You want your code to be able to create different representations of a product.
  3. You are dealing with complex objects that require several steps to construct.

The How

Here’s a step-by-step guide on how to implement the Builder Pattern:

  1. Define the Product: This is the complex object that will be constructed.

class Computer {
    private final String CPU;
    private final String RAM;
    private final int storage;
    private final boolean hasGraphicsCard;
    private final boolean hasSoundCard;

    private Computer(ComputerBuilder builder) {
        this.CPU = builder.CPU;
        this.RAM = builder.RAM;
        this.storage = builder.storage;
        this.hasGraphicsCard = builder.hasGraphicsCard;
        this.hasSoundCard = builder.hasSoundCard;
    }

    @Override
    public String toString() {
        return "Computer with CPU: " + CPU + ", RAM: " + RAM + ", storage: " + storage + "GB" +
                (hasGraphicsCard ? ", a graphics card" : "") +
                (hasSoundCard ? ", and a sound card" : "");
    }
}        

2. Create the Builder Interface: This interface will declare the construction steps.

interface ComputerBuilder {
    ComputerBuilder setCPU(String CPU);
    ComputerBuilder setRAM(String RAM);
    ComputerBuilder setStorage(int storage);
    ComputerBuilder setGraphicsCard(boolean hasGraphicsCard);
    ComputerBuilder setSoundCard(boolean hasSoundCard);
    Computer build();

    String getCPU();
    String getRAM();
    int getStorage();
    boolean hasGraphicsCard();
    boolean hasSoundCard();
}        

3. Implement Concrete Builders: These classes will provide different implementations of the construction steps.

class ConcreteComputerBuilder implements ComputerBuilder {
    private String CPU;
    private String RAM;
    private int storage;
    private boolean hasGraphicsCard;
    private boolean hasSoundCard;

    @Override
    public ComputerBuilder setCPU(String CPU) {
        this.CPU = CPU;
        return this;
    }

    @Override
    public ComputerBuilder setRAM(String RAM) {
        this.RAM = RAM;
        return this;
    }

    @Override
    public ComputerBuilder setStorage(int storage) {
        this.storage = storage;
        return this;
    }

    @Override
    public ComputerBuilder setGraphicsCard(boolean hasGraphicsCard) {
        this.hasGraphicsCard = hasGraphicsCard;
        return this;
    }

    @Override
    public ComputerBuilder setSoundCard(boolean hasSoundCard) {
        this.hasSoundCard = hasSoundCard;
        return this;
    }

    @Override
    public Computer build() {
        return new Computer(this);
    }

    @Override
    public String getCPU() {
        return CPU;
    }

    @Override
    public String getRAM() {
        return RAM;
    }

    @Override
    public int getStorage() {
        return storage;
    }

    @Override
    public boolean hasGraphicsCard() {
        return hasGraphicsCard;
    }

    @Override
    public boolean hasSoundCard() {
        return hasSoundCard;
    }
}        

4. Create a Director (Optional): This class defines the order of construction steps.

class ComputerDirector {
    public Computer buildGamingComputer(ComputerBuilder builder) {
        return builder.setCPU("Intel i9")
                      .setRAM("32GB")
                      .setStorage(1000)
                      .setGraphicsCard(true)
                      .setSoundCard(true)
                      .build();
    }

    public Computer buildOfficeComputer(ComputerBuilder builder) {
        return builder.setCPU("Intel i5")
                      .setRAM("16GB")
                      .setStorage(500)
                      .setGraphicsCard(false)
                      .setSoundCard(false)
                      .build();
    }
}        

5. Client Code: The client code creates both the builder and the director objects and initiates the construction process.

public class Main {
    public static void main(String[] args) {
        ComputerBuilder builder = new ConcreteComputerBuilder();
        ComputerDirector director = new ComputerDirector();

        Computer gamingComputer = director.buildGamingComputer(builder);
        Computer officeComputer = director.buildOfficeComputer(builder);

        System.out.println(gamingComputer);
        System.out.println(officeComputer);
    }
}        

UML diagram

Consequences and Tradeoffs

The Builder Pattern comes with its pros and cons.

Pros:

  1. Step-by-Step Construction: You can construct objects step-by-step and defer construction steps.
  2. Reusability: Reuse the same construction code for building various representations of products.
  3. Single Responsibility Principle: Isolate complex construction code from the business logic of the product.

Cons:

  1. Increased Complexity: The overall complexity of the code increases since the pattern requires creating multiple new classes.
  2. Builder Code Duplication: If there are many similar products, you might end up duplicating builder code.

Conclusion

The Builder Pattern is a powerful design pattern that helps manage the construction of complex objects. By separating the construction process from the object’s representation, it offers flexibility and scalability, making the code more readable and maintainable. Using our Computer example, we demonstrated how to implement the Builder Pattern in Java, highlighting its benefits and tradeoffs. With the Builder Pattern, you can construct complex objects in a more structured and manageable way.

Thank you

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

Chirag Vaswani的更多文章

  • Iterator Pattern

    Iterator Pattern

    WHAT IS ITERATOR PATTERN? Iterator pattern allows us to traverse elements of a collection without exposing its…

  • Command Pattern

    Command Pattern

    The Command design pattern encapsulates a request as an object, thereby allowing users to parameterize clients with…

  • Chain of Responsibility Pattern

    Chain of Responsibility Pattern

    Ever faced challenges managing complex workflows with multiple handlers in a serial manner? CoR to the rescue… THE WHAT…

  • Proxy Pattern

    Proxy Pattern

    Ever wondered how you can manage heavy resources in your applications without slowing down performance? There is way to…

  • Flyweight Pattern

    Flyweight Pattern

    Flyweight is a class in boxing which includes fighters weighing up to and including 51 kg (112 lb) for a title fight…

  • Facade Pattern

    Facade Pattern

    The What The Facade Pattern is a structural design pattern that provides a simplified interface to a complex subsystem,…

  • Decorator Pattern

    Decorator Pattern

    The Decorator Pattern is a structural design pattern that allows you to dynamically attach additional responsibilities…

  • Composite Pattern

    Composite Pattern

    Have you ever struggled with managing complex hierarchical structures in your code? Composite pattern can simplify your…

  • Bridge Pattern

    Bridge Pattern

    Ever felt overwhelmed by the explosion of subclasses when trying to support multiple variations in your code? What if…

  • Adaptor Pattern

    Adaptor Pattern

    The Adapter Pattern is about creating an intermediary abstraction that translates, or adapts, one interface into…

社区洞察

其他会员也浏览了