The REAL Builder Pattern
What is the Builder Pattern?
Even these bad articles got one thing right: The Builder Pattern is a Creational Pattern. This means that the pattern's main goal is to provide a reusable solution for creating objects. You may ask, why are there more than one creational pattern? Isn't invoking a class constructor enough? Well... yes and no.Why use Builder Pattern?
Experts (people who know more than me) have determined that using one of these creational patterns is preferred over invoking the constructor of a class directly. In order to keep this short, I am not going to get in depth regarding why creational patterns are preferred over invoking constructors directly. So, suffice to say that invoking constructors directly is fine for very simple objects needed to solve simple problems. As the complexity of one or both of these things increases, the need for a better solution than invoking the constructor directly is more and more apparent. Creational Patterns are not (necessarily) in competition with each other. Therefore, you could argue that there isn't one pattern that is better than another. They just simply have different usages. Because this article is just about the Builder Pattern, I am going to focus strictly on it. The main advantage of this pattern is that it allows a complex object to be built step by step.Consider an automobile assembly line. The vehicle being assembled is not built all at once. It is put together in stages. I am not an expert in automotive manufacturing, but I can assume you would start by taking a chassis and adding components to it: motor, transmissions, drive train, exhaust system, floor, body, electrical system (wiring), seats, windshield, windows, etc. The point is that the vehicle is put together piece by piece, component by component, until a fully-functional vehicle rolls out the factory. You could say that after all pieces are put together, you roll out an instance of vehicle.
As a side note, always remember to narrow the scope or responsibility of a class to a single thing (Single Responsibility Principle, or the 'S' in SOLID). For example, a motor is made of many smaller components. Therefore, if I was to model a vehicle class, instead of making every single part that composes an engine an attribute of vehicle, I would create a motor class that would encapsulate all these. This way, the vehicle class would only have a single "motor" attribute to be concerned with. This approach will make your code cleaner, and thus more maintainable. It will also make your Builder less verbose.
Implementing the Pattern
In my opinion, including a builder as a static nested class is probably the best approach.The image above is a representation of a Person class and its builder. When an instance of person needs to be created, the attributes of Person are passed to its builder. Once the builder's client passes the required attributes of person to it, it can invoke createPerson() in order to obtain an instance of the Person class.
Let's use the Vehicle class example and use the Builder Pattern to create an instance of it. The first thing we are going to do is to identify all of the required components and all the optional components. All required components must be declared as final. In the case of my simple Vehicle class, engine is required but stereo is optional. Therefore,
public class Vehicle
{
private final Engine engine; // final for immutability
private final StereoSystem stereo; // final for immutability
...
public static class Builder
{
private final Engine engine; // REQUIRED
private StereoSystem stereo; // OPTIONAL
public Builder(Engine engine)
{
this.engine = engine;
}
...
}
}
When using the Builder pattern, setter methods take a unique form. Setter method must return the current instance of builder
public Builder addStereo (StereoSystem stereo)
{
this.stereo = stereo;
return this;
}
Since we are putting the responsibility on the builder to provide an instance of Vehicle, we must prevent its constructor to be invoked directly (bypassing the builder) by making the constructor private. Now, even though Builder is a member of the Vehicle class, it doesn't have access to the Builder's internal attributes. This problem is solved by passing Builder to the Vehicle private constructor.
private Vehicle (Builder builder)
{
stereo = builder.stereo;
engine = builder.engine;
}
Lastly, the builder must provide a "build" method that returns an instance of the object being created. In this case, an instance of Vehicle.
public Vehicle build ()
{
return new Vehicle(this); // Vehicle instance finally committed to memory
}
public class Vehicle
{
private final Engine engine;
private final StereoSystem stereo;
private Vehicle (Builder builder)
{
stereo = builder.stereo;
engine = builder.engine;
}
public static class Builder
{
private final Engine engine; // REQUIRED
private StereoSystem stereo; // OPTIONAL
public Builder(Engine engine)
{
this.engine = engine;
}
public Builder addStereo (StereoSystem stereo)
{
this.stereo = stereo;
return this;
}
public Vehicle build ()
{
return new Vehicle(this); // Vehicle instance finally committed to memory
}
}
}
public static void main(String [] args)
{
// Engine and stereo creation omitted.
// Assume they are properly instantiated
Vehicle myCar = new Vehicle.Builder(engine)
.addStereo(stereo).build();
}
public static void main(String [] args)
{
// Engine and stereo creation omitted.
// Assume they are properly instantiated
Builder builder = new Vehicle.Builder(engine);
// Do something here...
builder.addStereo(stereo);
// Do something else...
Vehicle myCar = builer.build();
}
Comments
Post a Comment