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.
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
.
Respectively the only available method after Build
is the WithCase
since this is the next in chain method.