Introduction

Builder Design Pattern
Builder Design Patterns

The Builder Design Pattern is a type of Creational Design Pattern that simplifies the creation of complex objects. It separates the construction of an object from its representation, allowing the same construction process to create different representations. This pattern is useful when the creation of an object requires a complex process, such as the setting of multiple properties or the execution of complex algorithms.

The Builder Design Pattern involves creating a separate class, known as the builder class, to construct the object. The builder class defines the necessary steps to create the object, and the client code uses the builder to create the object. The builder class may also provide default values for properties, allowing the client code to create objects with a minimal amount of code.

Using the Builder design pattern can make code easier to read and modify. It also allows for better control over the construction process of complex objects, making it easier to modify or extend the process in the future.

Example using C Language

Let us understand the builder design pattern using an example of application that help us to build a computer. In this example, we want to build a computer with a specific configuration, such as the type of processor, the amount of RAM, and the size of the hard drive. We’ll use the builder pattern to build the computer with all these specific features.

First, we need to define our computer structure and create an interface for the builder.

// Define computer structure
typedef struct {
   char processor[30];
   int ram;
   int hdd;
} computer_t;

// Builder interface
typedef struct {
   void (*set_processor)(char*);
   void (*set_ram)(int);
   void (*set_hdd)(int);
   computer_t* (*get_computer)();
} builder_t;

Next, we need to create the builder function that sets the attributes of the computer.

// Builder function
void builder_set_processor(char* processor) {
    strcpy(computer.processor, processor);
}

void builder_set_ram(int ram) {
    computer.ram = ram;
}

void builder_set_hdd(int hdd) {
    computer.hdd = hdd;
}

computer_t* builder_get_computer() {
    return &computer;
}

We have defined the builder interface and created the builder functions. Now, we can create different builder objects for different types of computers. For example, we can create a builder for a gaming computer and a builder for a work computer.

// Gaming computer builder
void build_gaming_computer(builder_t* builder) {
    builder->set_processor("Intel Core i7");
    builder->set_ram(16);
    builder->set_hdd(512);
}

// Work computer builder
void build_work_computer(builder_t* builder) {
    builder->set_processor("Intel Core i5");
    builder->set_ram(8);
    builder->set_hdd(256);
}

Finally, we can use the builder to create different types of computers with their specific configurations.

int main() {
    builder_t builder;
    builder.set_processor = builder_set_processor;
    builder.set_ram = builder_set_ram;
    builder.set_hdd = builder_set_hdd;
    builder.get_computer = builder_get_computer;

    computer_t* gaming_computer;
    computer_t* work_computer;

    build_gaming_computer(&builder);
    gaming_computer = builder.get_computer();

    build_work_computer(&builder);
    work_computer = builder.get_computer();
}

In this example, we have used the builder pattern to create different types of computers with their specific configurations. By separating the construction of an object from its representation, we can create different objects with the same construction process. This makes the builder pattern a powerful tool for creating complex objects.

C++ Example

in C++, the above example can be done in bellow manner.

#include <iostream>
#include <cstring>

using namespace std;

// Define computer structure
struct computer_t {
   char processor[30];
   int ram;
   int hdd;
};

// Builder interface
class builder_t {
public:
    virtual void set_processor(char*) = 0;
    virtual void set_ram(int) = 0;
    virtual void set_hdd(int) = 0;
    virtual computer_t* get_computer() = 0;
};

// Builder function
class computer_builder_t : public builder_t {
public:
    void set_processor(char* processor) {
        strcpy(computer.processor, processor);
    }

    void set_ram(int ram) {
        computer.ram = ram;
    }

    void set_hdd(int hdd) {
        computer.hdd = hdd;
    }

    computer_t* get_computer() {
        return &computer;
    }

private:
    computer_t computer;
};

// Gaming computer builder
class gaming_computer_builder_t : public computer_builder_t {
public:
    void build() {
        set_processor("Intel Core i7");
        set_ram(16);
        set_hdd(512);
    }
};

// Work computer builder
class work_computer_builder_t : public computer_builder_t {
public:
    void build() {
        set_processor("Intel Core i5");
        set_ram(8);
        set_hdd(256);
    }
};

int main() {
    gaming_computer_builder_t gaming_builder;
    work_computer_builder_t work_builder;

    gaming_builder.build();
    computer_t* gaming_computer = gaming_builder.get_computer();

    work_builder.build();
    computer_t* work_computer = work_builder.get_computer();

    return 0;
}

In this C++ code, the computer structure is the same as the C code explained before, but the builder interface has been implemented as a C++ class with virtual methods. The builder function is now a C++ class as well, and it inherits from the builder interface. The gaming and work computer builders are also implemented as C++ classes, and they inherit from the builder function.

The set_processor, set_ram, set_hdd, and get_computer methods are now virtual functions that must be implemented by any class that inherits from the builder interface. The computer_builder_t class implements these functions, and the gaming and work computer builder classes inherit from computer_builder_t and implement the build function, which sets the properties of the computer.

Advantages & Disadvantages of Builder Design Pattern

Advantages

  • Allows you to construct complex objects step by step.
  • Provides better control over the construction process, allowing you to change it without affecting the final product.
  • Encapsulates the creation code and product details, making it easier to maintain and understand.
  • Allows you to vary the internal representation of the product.

Disadvantages

  • Requires the creation of a separate ConcreteBuilder for each product, which can lead to an increase in the number of classes
  • Adds complexity to the system, making it harder to understand for less experienced developers.
  • Can lead to a loss of efficiency in terms of memory usage and runtime, as it requires additional objects to be created and managed during construction.

Reference:

Related topics: