Design patterns Ep.3 - Creational - Builder

Design patterns Ep.3 - Creational - Builder

In the previous articles we explored details on the following patterns:

In today's article, we are going to examine another?creational?design pattern and in particular, the?Builder?pattern.

Problem

Let us understand the Builder Design Pattern with an example of a problem statement. Imagine we need to build a really complex object, requiring intensive step-by-step initialization of many fields and possible nested objects. Often, this type of initialization logic is gathered inside a really large constructor, which take as input many parameters.

For example, let's examine how we would construct a Car object. For the sake of simplicity let's say that in order to build a simple car object, we need to install a particular engine type, a number of comfortable seats and some doors in order to be able to enter it.

No alt text provided for this image

But what if, we would now want to have the ability to install various other features like, a GPS or a Trip Computer system?

The simplest solution that comes to mind, is to extend the base Car class and create a set of subclasses to cover all combinations of the newly introduced parameters. With this, you will eventually end up with a considerable amount of new subclasses (e.g. CarWithGPS, CarWithTripComputer, CarWithGPSAndTripComputer). Also, with any new parameter introduced (e.g. Voice Recognition system), you will need to extend the class hierarchy even more.

Another approach you can follow, in order to prevent creating many new subclasses for each parameter combination you want to support, is to create a very large constructor inside the base Car class containing all the possible parameters that control the Car object.

No alt text provided for this image

Using this approach, although you can eliminate the need for creating many new subclasses for every possible parameter combination, it introduces another problem. While in most cases, many of the parameters are going to be unused, the constructor calls are going to become pretty ugly. For example, only a fraction of cars will have GPS, Trip Computer and Voice Recognition systems installed, so those parameters will be useless most of the time.

No alt text provided for this image

Solution

We can fulfill the above requirements and solve the above problems by using the Builder Design Pattern, which is a creational design pattern that lets you?construct complex objects step by step. In other words, the builder pattern lets us extract the construction code of a complex object out of its class and move it to separate objects called builders, so that the same construction process can create different representations.

The pattern organizes object construction into a set of steps (SetSeats, SetEngineType, SetDoors). To create an object, you execute a series of steps on some builder object. The important part is that you don’t need to call all of the steps. You can only call those steps necessary for producing a particular representation of an object. Also, the Builder object doesn’t allow other objects to access the product while it’s being built.

Some of the construction steps might require different implementation when you need to build various representations of the product. In this case, you can create several different builder classes that implement the same set of building steps, but in a different manner. Then you can use these builders in the construction process (i.e., an ordered set of calls to the building steps) to produce different kinds of objects.

You can also extract a series of calls to the builder steps you use to construct a product into a separate class called director. The director class defines the order in which to execute the building steps, while the builder provides the implementation for those steps. Having a director class in your program is entirely optional. You can always call the building steps in a specific order directly from the client code, but having a director class gives you a good place to put various construction methods (e.g. ConstructSportsCar, ConstructSUVCar), so that you can reuse them inside your application. In addition, the director class completely hides the details of product construction from the client code. The client only needs to provide a builder object to the director, launch the construction process through calling a director's construction routine and finally get the result from the builder.

Let's examine some details of the pattern and see some code in C#.

Structure

No alt text provided for this image

Participants

  • Builder (IBuilder): Specifies an abstract interface for creating parts of a Product object. The Product construction steps are common to all types of builders.

No alt text provided for this image

You can define the methods of the IBuilder interface to either have a return type of void or a return type of the same interface (IBuilder), to achieve a method call chain approach in your client code. In our example we follow the second approach.

  • ConcreteBuilder (CarBuilder, CarManualBuilder): Constructs and assembles parts of the Product by implementing the Builder interface. It also defines and keeps track of the representation of the object it creates. Finally, it provides an interface for retrieving the final product it constructed (e.g. Build() method that returns the final Car or Manual objects). Concrete builders may produce products that don’t follow a common interface.

  1. CarBuilder source code
  2. CarManualBuilder source code

  • Product (Car, Manual): Represents the complex object under construction. ConcreteBuilder builds the product's internal representation and defines the process by which it's assembled. Products constructed by different builders do not have to belong to the same class hierarchy or follow the same interface.

No alt text provided for this image
No alt text provided for this image

  • Director (Director): Constructs an object using the Builder interface. It defines the order of the construction steps, lets you reuse specific configurations of products and decouples the client code from having to know how to use the builder objects. It is entirely optional (you can always create the builders and use the construction steps inside the client code directly).

No alt text provided for this image

  • Client: Either creates a ConcreteBuilder object and calls its construction steps directly, in order to get back a final Product. Or it may also pass one of the ConcreteBuilder objects inside a Director instance. Then the director uses that builder object for all further construction.

No alt text provided for this image

The output of the above code for the "ConstructSportsCar" method call of the director on both CarBuilder and CarManualBuilder objects would be:

No alt text provided for this image
No alt text provided for this image

On the other hand, if we changed our Main method's code and called the "ConstructSUV" method of the Director class, the output would be:

No alt text provided for this image
No alt text provided for this image

Applicability

Use the Builder pattern when:

1) The algorithm for creating a complex object should be independent of the parts that make up the object and how they're assembled.

2) The construction process must allow different representations for the object that's constructed.

You can find the source code for the above example?here.

That's all for today. Cheers!

Theofilos Tougountzoglou

Manager in Technology Consulting at EY

3 年

Great job Orestis Meikopoulos !!Totally recommend it !

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

社区洞察

其他会员也浏览了