Skip to content

Builder Pattern

MidasXIV edited this page Aug 18, 2023 · 7 revisions

Builder Pattern

The intent of the Builder Pattern is to facilitate the construction of complex objects by separating the construction process from the actual representation. By using the Builder Pattern, you can simplify the process of creating complex objects, reduce constructor overloading, and improve the maintainability of your codebase. It's particularly beneficial when dealing with objects that have multiple optional or mandatory parameters and require multiple steps to be properly configured and created.

In essence, the Builder Pattern provides a structured approach to constructing objects, making your code more organized, flexible, and easier to read. It's a valuable tool in your design pattern toolbox when dealing with complex object creation scenarios.

The easiest way to understand the intent of the Builder pattern is to look at examples of where it is useful.

Let's say you wanted to use a method or a subroutine that someone else has written because it performs some function that you need, but you cannot incorporate the routine directly into your program;

As it maybe a third party software so you cannot make changes to their code and also you don’t want to solve the problem by changing your existing code.

Or simply the interface or the way of calling the routine is not exactly consistent or equivalent to the way that is related objects need to use it.


Steps to implementing the adapter pattern.

AGENDA : The Adapter pattern allows otherwise incompatible classes to work together by converting the interface of one class into an interface expected by the clients.

  1. First we Identify the complex object(s) you want to create. Define its attributes, properties, and possible configurations. This is the object that the builder will construct.

  2. Then identify the Builder Interface, Design an abstract interface or class that defines the methods for constructing and configuring the parts of the complex object. These methods should cover various aspects of object creation.

  3. Create one or more concrete classes that implement the builder interface. Each concrete builder should provide methods to build different parts of the complex object and assemble them.

  4. Create the Director (Optional): Consider implementing a director class that coordinates the construction process using a builder. The director can provide a higher-level interface for building objects and managing the order of construction steps.

  5. Client Code: In the client code, create an instance of the builder you want to use. Use the builder's methods to set the attributes and configurations of the complex object.

  6. Build the Object: Call the builder's methods to construct the complex object step by step. The builder handles the construction details, ensuring that the final object is properly initialized.

  7. Obtain the Result: After constructing the object, use a method provided by the builder to obtain the final product. This method should return the fully constructed and configured complex object.

Use the Complex Object: Once you have the constructed complex object, you can use it as needed within your application.


Example

// Step 1: Define the Product
class Car {
  constructor(public make: string, public model: string, public year: number) {}
}

// Step 2: Create the Builder Interface
interface CarBuilder {
  setMake(make: string): void;
  setModel(model: string): void;
  setYear(year: number): void;
  build(): Car;
}

// Step 3: Implement Concrete Builders
class SedanBuilder implements CarBuilder {
  private car: Car;

  constructor() {
    this.car = new Car("Unknown", "Unknown", 0);
  }

  setMake(make: string): void {
    this.car.make = make;
  }

  setModel(model: string): void {
    this.car.model = model;
  }

  setYear(year: number): void {
    this.car.year = year;
  }

  build(): Car {
    return this.car;
  }
}

// Step 5: Client Code
const sedanBuilder = new SedanBuilder();
sedanBuilder.setMake("Toyota");
sedanBuilder.setModel("Camry");
sedanBuilder.setYear(2022);

// Step 6: Build the Object
const car: Car = sedanBuilder.build();

// Step 7: Obtain the Result
console.log(car);

Types of Adapter Pattern

There are actually two types of Adapter patterns:

  1. Object Adapter pattern :
  • The composition approach or the "has a" approach to implementing the adapter pattern.
  • In this approach the adapter/wrapper class relies on one object of the adaptee class.
  • So when a adapter/wrapper class object is instantiated, it must instantiate a corresponding adaptee class object. Anything the adapter/wrapper object is told to do will get passed to the adaptee class object.
  1. Class Adapter pattern :
  • The inheritance approach or the "is a" approach to implementing the adapter pattern.
  • In this approach we create a new class which derives publicly from our abstract class to define it's interface and derives privately from our existing class to access it's implementation.
  • So in simpler terms unlike the Object Adapter pattern which creates an object of the adaptee class and simply passes any function invocation to the adaptee class object, the Class Adapter pattern since it extends/derives from the adaptee class it is able to call the methods of the adaptee class from within it's new interface. (basically call adaptee class methods from it's own custom methods without an object.)

When to use Adapter pattern over other patterns

When there's preexisting classes. (client and adaptee class)
When there's a client class-interface the adapter must be designed to.
To keep this article very simple and concise I'll try not to overload the article with other patterns.But at a very high level the Facade and the Adapter pattern seem similar. As they both have preexisting classes and at a high level we do create a wrapper class.
But ultimately what you should keep in mind A Facade simplifies an interface while an Adapter converts a preexisting interface into another interface.
More about Facade pattern in the next articles.

Clone this wiki locally