Design Pattern: Builder and Car

Design Pattern: Builder and Car

Design patterns in life and Ruby — gain an intuitive understanding of OO design patterns by linking them with real-life examples.

The builder pattern is a very commonly used pattern. But its definition can be a bit confusing at first glance.

The builder pattern separates the construction of a complex object from its representation so that the same construction process can create different representations.

Fear not. The definition will become much clearer after we build some cars. ? ? ? ?

Let’s start by building a standard car.

class StandardCarBuilder
  def build
    car = ''
    # 1. build car frame
    car << "This is a standard car\n"
    # 2. add an engine
    car << "  with an engine\n"
    # 3. add front wheels
    car << "  with two front wheels\n"
    # 4. add back wheels
    car << "  with two back wheels\n"
    # 5. add dashboard
    car << "  with a dashboard\n"
    # 6. add a energy source
    car << "  with a fuel tank\n"
    # return the car
    car
  end
end

To keep our example simple, we will only pay attention to car frames, engines, wheels, dashboard, and energy sources and ignore the rest.

The builder is easy to use.

Let’s also build a sports version of the car.

class SportsCarBuilder
  def build
    car = ''
    # 1. build car frame
    car << "This is a sport car\n"
    # 2. add an engine
    car << "  with a powerful engine\n"
    # 3. add front wheels
    car << "  with two front wheels suitable for mountain paths\n"
    # 4. add back wheels
    car << "  with two back wheels suitable for mountain paths\n"
    # 5. add dashboard
    car << "  with a fancy sporty dashboard\n"
    # 6. add a energy source
    car << "  with a big fuel tank\n"
    # return the car
    car
  end
end

The sports version has a more powerful engine, wheels suitable for mountain paths, a fancier dashboard, and a bigger fuel tank.

We can use the SportsCarBuilder the same way we use StandardCarBuilder.

We also want to provide an environmentally friendly option: an electronic car.

class ElectronicCarBuilder
  def build
    car = ''
    # 1. build car frame
    car << "This is an electronic car\n"
    # 2. add an engine
    car << "  with a electronic engine\n"
    # 3. add front wheels
    car << "  with two front wheels\n"
    # 4. add back wheels
    car << "  with two back wheels\n"
    # 5. add dashboard
    car << "  with a dashboard showing current battery level\n"
    # 6. add a energy source
    car << "  with a battery\n"
    # return the car
    car
  end
end

The electronic version has an electronic engine, standard wheels, a dashboard that shows the current battery level, and a battery.

We can use the ElectronicCarBuilder the same way we use the above two builders.

Take a Step Back

Take a look at all three car builders. Notice the process they use to construct cars is the same:

  1. Build a car frame
  2. Add an engine
  3. Add front wheels
  4. Add back wheels
  5. Add a dashboard
  6. Add an energy source
  7. Return the car

The part that varies is the implementation. For example, all three builders implement step 2, “Add an engine”, differently. The StandardCarBuilder adds a standard engine. The SportsCardBuilder adds a more powerful engine. The ElectronicCarBuilder adds an electronic engine.

Time to Refactor Our Code

We can refactor these three builders to follow a design principle we talked about earlierencapsulate what varies and separate it from the stable part of the code.

The stable part of the code is the process we use to construct a car. The part that varies is the implementation of each step in the process. We want to separate the construction process from the implementation.

Let’s start by reducing each builder to only contain the implementation of each step.

class StandardCarBuilder
  attr_reader :car
  
  def initialize
    @car = ''
  end
  
  def build_car_frame
    car << "This is a standard car\n"
  end
  
  def add_engine
    car << "  with an engine\n"
  end
  
  def add_front_wheels
    car << "  with two front wheels\n"
  end

  def add_back_wheels
    car << "  with two back wheels\n"
  end
  
  def add_dashboard
    car << "  with a dashboard\n"
  end
  
  def add_energy_source
    car << "  with a fuel tank\n"
  end
end

The new version of the StandardCarBuilder creates an empty car and provides six methods, build_car_frameadd_engineadd_front_wheelsadd_back_wheelsadd_dashboard, and add_energy_source, that can be used to build a car.

Similarly, the new version of the SportsCarBuilder and ElectronicCarBuilder look like the following.

class SportsCarBuilder
  attr_reader :car
  
  def initialize
    @car = ''
  end
  
  def build_car_frame
    car << "This is a sport car\n"
  end
  
  def add_engine
    car << "  with a powerful engine\n"
  end
  
  def add_front_wheels
    car << "  with two front wheels suitable for mountain paths\n"
  end

  def add_back_wheels
    car << "  with two back wheels suitable for mountain paths\n"
  end
  
  def add_dashboard
    car << "  with a fancy sporty dashboard\n"
  end
  
  def add_energy_source
    car << "  with a big fuel tank\n"
  end
end
class ElectronicCarBuilder
  attr_reader :car
  
  def initialize
    @car = ''
  end
  
  def build_car_frame
    car << "This is an electronic car\n"
  end
  
  def add_engine
    car << "  with a electronic engine\n"
  end
  
  def add_front_wheels
    car << "  with two front wheels\n"
  end

  def add_back_wheels
    car << "  with two back wheels\n"
  end
  
  def add_dashboard
    car << "  with a dashboard showing current battery level\n"
  end
  
  def add_energy_source
    car << "  with a battery\n"
  end
end

With these three builders, all we need is a class that uses a builder to construct a car.

class CarConstructionDirector
  def construct_car(builder:)
    builder.build_car_frame
    builder.add_engine
    builder.add_front_wheels
    builder.add_back_wheels
    builder.add_dashboard
    builder.add_energy_source
    builder.car
  end
end

The CarConstructionDirector has a construct_car method that takes a builder and uses it to construct a car.

With three new car builders and a car construction director, we can construct cars like this:

First, initialize a director and builders.

Second, construct cars by passing builders to the director’s construct_car method.

And here are the cars we create.

Revisit The Builder Pattern

The builder pattern separates the construction of a complex object from its representation so that the same construction process can create different representations.

In our car construction example, we separated the construction of a car from its representation. The construction process is in the construct_car method of the CarConstructionDirector. The representations of the cars are in the builders: StandardCarBuilderSportsCarBuilder, and ElectronicCarBuilder.

And we can use the same construction process, a.k.a the construct_carmethod, to create different types of cars.

??Woohoo, we just learned the builder pattern!??

Keys of The Builder Pattern

There are two key parts of the builder pattern.

1. The builder pattern is used for constructing COMPLEX objects.

Using the builder pattern in our example might seem like overkill. That’s because in our example we simplified the implementations of the constructing steps. If we were actually to construct a real car, each step would be much more complicated. For example, just the add_an_engine step alone could contain hundreds of lines of code, instead of car << “ with an engine\n”.

If you are indeed trying to construct a complex object, consider the builder pattern. If the object you need is simple, the builder pattern might be overkill.

2. The goal of the builder pattern is to use the same construction process to create different representations of the same type of object.

In our example, we have three different representations of a car: standard, sports, and electronic.

If you have more than one representation of the object you need to construct, consider the builder pattern. Otherwise, it might be overkill.

 

Object-Oriented Design Concepts in The Builder Pattern

There are two important object-oriented design concepts used in the builder pattern that are worth pointing out.

1. The “encapsulate what varies” design principle.

The builder pattern uses each concrete builder (StandardCarBuilderSportCarBuilder, and ElectronicCarBuilder) to encapsulates the parts that vary.

2. Dependency Injection

Dependency injection is a technique for achieving a loose coupling between objects and their collaborators, or dependencies.

In our example, the construct_car method has a dependency on a car builder. And we inject this dependency by passing a builder into the method.

Builder vs Template Method

The builder pattern might have reminded you of the template method pattern.

Take a look at the definition of the template method pattern. They are indeed similar.

The Template Method pattern is a behavioral design pattern that
– defines the program skeleton of an algorithm in an operation,
– deferring some steps to subclasses.
It lets one redefine certain steps of an algorithm without changing the algorithm’s structure.

They both separate the process of doing something from the implementation of each step in the process.

But they have two main differences.

1. The way they separate the process from the implementation of each step is different.

As we mentioned before, the builder pattern uses dependency injection to achieve this separation. But the template method pattern uses inheritance to achieve the separation.

2. The purpose of each pattern is different.

The builder pattern is a creational pattern specifically designed for creating objects. The template method pattern is a behavioral pattern with a more general purpose.

Now here comes an important question: which car do you want to buy? ?


1 Comment Design Pattern: Builder and Car

Leave A Comment

Your email address will not be published. Required fields are marked *