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.

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.

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.

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.

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.

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

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? 🙃

Enjoyed the article?

My best content on Software Design, Rails, and Career in Dev. Delivered weekly.

Unsubscribe at anytime. I'll never spam you. Powered by ConvertKit

Leave a Comment