Manage Builder pattern access requirements (limit the public methods!) via nested Builder class in C#
The Builder Pattern
The Builder pattern is a creational design pattern that allows construction of complex objects step by step.
This pattern helps manage the complexity of creating a complex class that has many different ways of constructing and configuring it.Example - without Builder:
// constructing a House object with different configurations:
var myHouseWith2WindowsAndFlatRoof = new House(windows: 2, isFlatRoof: true);
var myHouseWith4WindowsAndGarageAndSlopedRoof = new House(windows: 4, isFlatRoof: false, new Garage());
With Builder:
var myHouseWith2WindowsAndFlatRoof = HouseBuilder.WithWindows(2).WithFlatRoof().Build();
var myHouseWith4WindowsAndGarageAndSlopedRoof = HouseBuilder.WithWindows(4).WithSlopedRoof().WithGarage().Build();
// Builder allows for more flexibility:
var houseBuilderWithSlopedRoof = HouseBuilder.WithWindows(4).WithSlopedRoof();
var house = needsGarage ? houseBuilderWithSlopedRoof.WithGarage().Build() : houseBuilderWithSlopedRoof.Build();
A few advantages of Builder:
Disadvantages:
Challenge: the Builder requires access to members of the class that it is creating
A challenge of implementing the Builder pattern is that in order to build the other class, it obviously needs enough access to be able to initialise that type. This introduces a risk of an extended API, adding more methods or property setters that are only required by the Builder. Such methods could always misused later, for example allowing undesirable alterations to be made to the built object, elsewhere in the code.
// Example: For House to be used by a Builder, it exposes a default constructor and a setter for Windows. The setter could be misused elsewhere in the code.
class House
{
// default constructor and methods that SHOULD only be used by Builder - but how to prevent misuse?
public House() {}
public int Windows { get; set; }
}
Solution in C#: make the Builder be a nested class of the class that it is builder.
Once the Builder is nested inside the class that it creates, it can access whatever methods or properties are required, without having to alter the API of the class. So, the API of the class is kept clean.However, this does require some discipline when building the Builder, that it does not make incorrect use of the class that it is building. Unit tests help!
class House
{
public static HouseBuilder Builder() => new HouseBuilder();
class HouseBuilder
{
House house = new House();
House Build() => return house;
HouseBuilder WithWindows(int windows)
{
house.Windows = windows;
return this;
}
HouseBuilder WithFlatRoof() {}
HouseBuilder WithSlopedRoof() {}
}
// private constructor and methods only accessible to Builder:
House() {}
public int Windows { get; private set; }
}
// Usage:
var myHouseWith2WindowsAndFlatRoof = House.Builder().WithWindows(2).WithFlatRoof().Build();
Alternative solution in C#: add an interface to the class that is only used by the Builder.
If a nested class seems to make the created class too big and have too much responsibility, then another option could be to add an interface that is only used by the Builder, to configure that class.
Discipline is needed to ensure that interface is not misused elsewhere in the codebase...
Summary: there are tradeoffs
Builder is, like many design patterns, useful when appropriately applied. It comes with its own advantages and disadvantages, and has its own implementation challenges depending on which language you are using.Related articles
see this refactoring guide
Comments
Post a Comment