Fluent Builder

Another way to implement the builder pattern is with fluent builder. Fluent builder provides a more readable and straightforward way to create objects. The pattern is implemented by having a series of methods that return the builder type itself and a build method that returns an instance of the class we are building.
For our example we are going to create a Desktop PC builder.
Bellow is the sample code:

public interface IDesktopBuilder
{
    IDesktopBuilder WithCase(string @case);
    IDesktopBuilder WithCpu(string cpu);
    IDesktopBuilder WithMotherBoard(string motherboard);
    IDesktopBuilder WithVideoCard(string videoCard);
    IDesktopBuilder WithOperatingSystem(string operatingSystem);
    Desktop Build();
}
public class DesktopBuilder : IDesktopBuilder
{
    private readonly Desktop _desktop;

    public DesktopBuilder()
    {
        _desktop = new Desktop();
    }

    public IDesktopBuilder WithCase(string @case)
    {
        _desktop.Case = @case;
        return this;
    }

    public IDesktopBuilder WithCpu(string cpu)
    {
        _desktop.Cpu = cpu;
        return this;
    }

    public IDesktopBuilder WithMotherBoard(string motherboard)
    {
        _desktop.Motherboard = motherboard;
        return this;
    }

    public IDesktopBuilder WithVideoCard(string videoCard)
    {
        _desktop.VideoCard = videoCard;
        return this;
    }

    public IDesktopBuilder WithOperatingSystem(string operatingSystem)
    {
        _desktop.OperatingSystem = operatingSystem;
        return this;
    }

    public Desktop Build()
    {
        return _desktop;
    }
}
public class DesktopPc
{
    public string Case { get; set; }
    public string Motherboard { get; set; }
    public string Cpu { get; set; }
    public string VideoCard { get; set; }
    public string OperatingSystem { get; set; }
}

Finally we can create a Desktop PC by using the fluent builder by the following way

DesktopPc desktop = new DesktopBuilder()
    .WithCase("Mini Tower Case")
    .WithCpu("3.6Ghz 12 cores")
    .WithMotherBoard("Gaming board")
    .WithVideoCard("Gaming v3 12GB")
    .WithOperatingSystem("64bit OS")
    .Build();

Or if we do not want to add an Operating System in our Desktop setup

DesktopPc desktop = new DesktopBuilder()
    .WithCase("Mini Tower Case")
    .WithCpu("3.6Ghz 12 cores")
    .WithMotherBoard("Gaming board")
    .WithVideoCard("Gaming v3 12GB")
    .Build();

Progressive Builder (progressive interface)

Although the fluent builder pattern gives us a more readable way to create an object, there is a drawback with the above approach. We can't tie the way we construct the object, for example if there is a need to select motherboard before adding a CPU. Also even though we have already used a method, this method remains available and we can use it again as shown in the image bellow.

image.png

To achieve such a behavior we can utilize the Progressive Builder pattern. With progressive interface we can use multiple builder patterns in a sequential way. This allow us to chain in a fixed sequence the method calls. The advantage of this implementation is that we ensure that the object is built in the correct order.
For example we want to build a desktop pc but in the following order.

  • Select a case
  • Select a motherboard
  • Select a CPU
  • Select a video card
  • Select operating system

For this to be done we will create separate interface for each desktop property and everyone of them is going to have a returning type the interface that comes next in the chain.

Bellow is the sample code:

public interface IProgressiveBuilder
{
    ICaseBuilder WithCase(string @case);
}

public interface ICaseBuilder
{
    IMotherboardBuilder WithMotherboard(string motherboard);
}

public interface IMotherboardBuilder
{
    ICpuBuilder WithCpu(string cpu);
}

public interface ICpuBuilder
{
    IVideoCardBuilder WithVideoCard(string videoCard);
}

public interface IVideoCardBuilder
{
    IOperatingSystemBuilder WithOperatingSystem(string operatingSystem);
}

public interface IOperatingSystemBuilder
{
    ProgressiveDesktop Create();
}
public class ProgressiveDesktop :
    IProgressiveBuilder,
    ICaseBuilder,
    IMotherboardBuilder,
    ICpuBuilder,
    IVideoCardBuilder,
    IOperatingSystemBuilder
{
    public string Case;
    public string Motherboard;
    public string Cpu;
    public string VideoCard;
    public string OperatingSystem;

    // Keep constructor private to avoid 
    // instantiation of the class outside of the builder
    private ProgressiveDesktop()
    {

    }

    public static IProgressiveBuilder Build()
    {
        return new ProgressiveDesktop();
    }

    public ICaseBuilder WithCase(string @case)
    {
        Case = @case;
        return this;
    }

    public IMotherboardBuilder WithMotherboard(string motherboard)
    {
        Motherboard = motherboard;
        return this;
    }

    public ICpuBuilder WithCpu(string cpu)
    {
        Cpu = cpu;
        return this;
    }

    public IVideoCardBuilder WithVideoCard(string videoCard)
    {
        VideoCard = videoCard;
        return this;
    }

    public IOperatingSystemBuilder WithOperatingSystem(string operatingSystem)
    {
        OperatingSystem = operatingSystem;
        return this;
    }

    public ProgressiveDesktop Create()
    {
        return this;
    }
}
// Build desktop object with the use of progressive builder
ProgressiveDesktop desktop =  ProgressiveDesktop
            .Build()
            .WithCase("Mini Tower Case")
            .WithMotherboard("Gaming board")
            .WithCpu("3.6Ghz 12 cores")
            .WithVideoCard("Gaming v3 12GB")
            .WithOperatingSystem("64bit OS")
            .Create();

Now when creating a progressive desktop the only available method is the Build. image.png Respectively the only available method after Build is the WithCase since this is the next in chain method. image.png

Did you find this article valuable?

Support Theodoros Karropoulos by becoming a sponsor. Any amount is appreciated!