I'm using the builder pattern, have extract repeated code into a 'helper' class but there's one aspect of repeated code I'm still not happy with.
The builder pattern allows one to chain implementation code like this:
Car car = new CarBuilder().Wheels(4).Convertible().Build();
Each of the methods CarBuilder
, Wheels
and Convertible
return the same instance of the builder class (return this
) and the Build
method return the newly-instantiated Car
.
Here's my attempt at a generic builder class:
public class Builder<T> where T : class
{
private Func<T, T> func;
protected void SetInstantiator(Func<T, T> f) => this.func = f;
protected void Chain(Action<T> action)
{
this.ChainFunc(action);
}
private ChainFunc(Action<T> action)
{
// SNIPPED
}
protected T Instantiate() => this.func(null);
}
And here's an implementation of my generic builder:
public class CarBuilder : Builder<Car>
{
public CarBuilder()
{
this.SetInstantiator(c => new Car());
return this;
}
public CarBuilder Wheels(int wheels)
{
this.Chain(c => c.SetWheelCount(wheels));
return this;
}
public CarBuilder Convertible()
{
this.Chain(c => c.RetractableRoof = true);
return this;
}
public Car Build() => this.Instantiate();
}
What is bothering me is the repeated return this
after each call to the Chain
method and thought I could push this into the Chain
method itself i.e. I want to write code like this:
public CarBuilder Wheels(int wheels) =>
this.Chain(c => c.SetWheelCount(wheels));
In the builder class I tried changing the return type from void
to Builder
:
protected Builder Chain(Action<T> action)
{
this.ChainFunc(action);
return this;
}
... but the compiler says the return type has to be Builder<T>
i.e.
protected Builder<T> Chain(Action<T> action)
{
this.ChainFunc(action);
return this;
}
OK, fair enough, but in my implementation class I now have to do a cast:
public CarBuilder Wheels(int wheels) =>
(CarBuilder)this.Chain(c => c.SetWheelCount(wheels));
So again I have repeated code in that all methods must now include a cast. Passing the class type from subtype to supertype doesn't feel right.
I think I might be missing something fundamental here. Can I avoid both repeating the cast and having to 'return this' from every builder implementation method?