This post is part of the series SOLID Wash Tunnel.

Definition

Builder Pattern

"Builder is a creational design pattern that lets you construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code."

- Refactoring Guru

An object can be initialized in many different ways. By far the most common way, is through the constructor. Depending on what arguments you supply (or leave out), the state of the object may very well be different.

This is usually not a problem for classes that have a small number of parameters, but becomes a beast to tame, when the number of parameters is high. On top of that, taking into consideration the synergistic effect of the individual parameters, the combination of states that arise, results in a huge number of constructor overloads to support all these states.

The Builder pattern lets you construct the object step-by-step. You could defer execution of some steps without breaking the final result. You can even call steps recursively, which comes in handy when you need to build an object tree.

Topping it all off, the builder does not expose an unfinished object while running construction steps. This prevents the client code from retrieving an incomplete object.

The builder pattern enables capabilities like:

  • Shows an objects intent much better to the consumer of the API.
  • Clear separation between the construction and representation of an object.
  • Better control over construction process, and the end result.
  • Supports changing the internal representation of the object.
  • Overcomes the need for constructor overloading.

But it also comes with some drawbacks like:

  • Loss of static analysis provided by the compiler. This results in detecting missing mandatory parameters only at runtime.
  • For classes with small number of parameters, it becomes an overkill.
  • Code is harder to debug.
  • Results in more code.

UML class diagram. [source]

Fluent Interfaces

"A fluent interface is an object-oriented API whose design relies extensively on method chaining. Its goal is to increase code legibility by creating a domain-specific language (DSL)."

- Wikipedia

A fluent interface simplifies the way we use an object's API and promotes method chaining. It is a very useful technique to make an interface of a class more expressive and easier to use for clients.

It is worth mentioning that fluent interface does not equate to method chaining. Method chaining is one way of implementing a fluent syntax.

This technique, paried with the builder pattern, enables a greater expression of intent, and an "english-like" way of object initialization. A fluent interface can be applied in different ways, depending on the object it is being applied on.

  • Object is immutable - Modifying each member of the object, returns a new instance of it with the appropriate change applied.
  • Object is mutable - Modifying each member of the object, mutates the object upon which it is invoked and returns the same instance with the appropriate change applied.

The builder pattern has been implemented in 3 distinct parts of the codebase:

  • UserPanel - Collects client information to start the vehicle wash process, and stores those in memory.
  • InvoiceBuilder - Retrieves the collected information from memory, and builds an invoice for the client.
  • CustomWashProgramBuilder - Allows the client to create a custom wash program, that can afterwards be executed by the tunnel.

Since they all work in conjuction with one another to deliver the end goal, we will elaborate all of them, sepparated into 3 articles as a mini-series (this being the first one).


Implementation of User Panel builder

The UserPanel is the entry point that the client uses to interact with the wash tunnel. The interfaces that it implements, are seggragated, with each of them having a narrow focus. They also represent a chain in the build process via the fluent interface technique.

IUserPanel is the first of the interfaces. It enables the client to either:

  • Select one of the available built-in wash programs, via the specified ProgramType.
  • Select a customized wash program, by providing an IWashProgram.

The client can not select both of them, because the return type of either method is an ICustomerInformationCollector. This is where the method chaining comes into play.

public interface IUserPanel
{
ICustomerInformationCollector SelectBuiltInProgram(ProgramType type);
ICustomerInformationCollector SelectCustomizedProgram(IWashProgram program);
}

public enum ProgramType
{
Custom,
Fast,
Economic,
AllRounder
}

The ICustomerInformationCollector enables the wash tunnel to collect further client information. This is needed to apply a discount on the invoice.

The client can be an individual or company, but not both at the same time, because the return type of either method is an IWashProcessStarter. Again method chaining on action!

public interface ICustomerInformationCollector
{
IWashProcessStarter AsIndividual(string firstName, string lastName, Currency preferedCurrecy);
IWashProcessStarter AsCompany(string companyName, Currency preferedCurrecy);
}

In the end, the client calls Start (the only method of IWashProcessStarter). The client supplies any type that implements IVehicle as a method argument. It also supplies an action type delegate, which is a callback to receive the printed invoice.

public interface IWashProcessStarter
{
void Start(IVehicle vehicle, Action<string> invoiceCallback);
}

Below you can see the full implementation of UserPanel.

ISignalTransmitter and IWashProgramFactory are dependencies that we inject. If the client has selected a built-in wash program, we use the factory to create an instance of it. If the client has selected a custom wash program, we invoke the builder's Build method, which returns an instance of IWashProgram.

Notice also how on each building step, we are transmiting signals. These signals behind the scenes are being handled, more specifically they are being stored in memory. They will be retrived from memory when the invoice will be generated.

public class UserPanel : 
IUserPanel,
ICustomerInformationCollector,
IWashProcessStarter

{
private readonly ISignalTransmitter _transmitter;
private readonly IWashProgramFactory _programFactory;

public UserPanel(
ISignalTransmitter transmitter,
IWashProgramFactory programFactory)
{
_transmitter = transmitter;
_programFactory = programFactory;
}

public ICustomerInformationCollector SelectBuiltInProgram(ProgramType type)
{
IWashProgram program = _programFactory.Create(type);
_transmitter.Transmit(new WashProgramSelectedSignal(program));

return this;
}

public ICustomerInformationCollector SelectCustomizedProgram(IWashProgram program)
{
_transmitter.Transmit(new WashProgramSelectedSignal(program));
return this;
}

public IWashProcessStarter AsIndividual(string firstName, string lastName, Currency preferedCurrecy)
{
_transmitter.Transmit(new IndividualCustomerInfoEnteredSignal(firstName, lastName, preferedCurrecy));
return this;
}

public IWashProcessStarter AsCompany(string companyName, Currency preferedCurrecy)
{
_transmitter.Transmit(new CompanyCustomerInfoEnteredSignal(companyName, preferedCurrecy));
return this;
}

public void Start(IVehicle vehicle, Action<string> invoiceCallback)
{
_transmitter.Transmit(new VehicleWashingStartedSignal(vehicle, invoiceCallback));
}
}

Invoking the builder

The UserPanel builder is invoked through the IUserPanel interface from Program.cs which is located in the SOLIDWashTunnel.Customers project.

Invocation for an individual with a built-in program.

var panel = container.GetService<IUserPanel>();

panel.SelectBuiltInProgram(ProgramType.Fast)
.AsIndividual("Ledjon", "Behluli", Currency.USD)
.Start(Vehicle, PrintInvoice());

Invocation for an individual with a customized program.

var panel = container.GetService<IUserPanel>();

panel.SelectCustomizedProgram(customProgram) // more on this in the next article
.AsIndividual("Ledjon", "Behluli", Currency.USD)
.Start(Vehicle, PrintInvoice());

Invocation for a company with a built-in program.

var panel = container.GetService<IUserPanel>();

panel.SelectBuiltInProgram(ProgramType.Fast)
.AsCompany("Ledjon SoftTech", Currency.USD)
.Start(Vehicle, PrintInvoice());

Invocation for a company with a customized program.

var panel = container.GetService<IUserPanel>();

panel.SelectCustomizedProgram(customProgram) // more on this in the next article
.AsCompany("Ledjon SoftTech", Currency.USD)
.Start(Vehicle, PrintInvoice());

Analogy to the builder pattern

Lets connect the concepts defined in the UML diagram of the builder pattern, to the UserPanel.

  • The joint effect of IUserPanel, ICustomerInformationCollector and IWashProcessStarter interfaces is analogous to Builder. It represents the abstraction, which in our case has been further seggragated into multiple interfaces to promote narrow focus and specialized responsibilities.
  • UserPanel is analogous to ConcreteBuilder. It represents the concrete implementation of Builder, or in our case the implementation of all the interfaces mentioned above.
  • Program.cs (or calling code) is analogous to Director.
  • The transmission of a VehicleWashingStartedSignal is analogous to Product.

Principles


PrincipleAppliedExplanation
SRPUserPanel (although having multiple methods) has one responsibility, that is to transmit a VehicleWashingStartedSignal. In order to do that, it needs to collect all neccessary client informations.
OCPUserPanel is closed for modification, but open for extension. Even if new wash programs are introduced (either built-in or external) the class does not have to change.
LSPThere is no inheritance involved, so LSP has no applicability.
ISPThe Builder abstraction has been seggragated into multiple interfaces IUserPanel, ICustomerInformationCollector and IWashProcessStarter.
DIPThe high level module UserPanel does not depend on the low level modules FastWashProgram, CustomWashProgramBuilder and Motherboard. It dependes on the abstractions IWashProgramFactory, IWashProgram and ISignalTransmitter respectively.

Continue the series on Fluent Builder (part 2/3).

If you found this article helpful please give it a share in your favorite forums 😉.
The solution project is available at GitHub.