Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Returning `this` from generic parent class requires cast in child

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?

like image 908
petemoloy Avatar asked Jan 31 '26 14:01

petemoloy


1 Answers

One way to keep the logic in the protected scope, is to add a static method that is called instead of the instance method. The static method can use implicit casting to return the type of the caller

Inside Builder<T>

protected void Chain(Action<T> action)
{
    //local chain logic
}

protected static BT Chain<BT>(BT builder, Action<T> action)
    where BT:Builder<T>
{
    builder.Chain(action);
    return builder;
}

Calls inside CarBuilder:

public CarBuilder Wheels(int wheels) => Chain(this , c => c.SetWheelCount(wheels));

public CarBuilder Convertible() => Chain(this, c => c.RetractableRoof = true);
like image 125
Me.Name Avatar answered Feb 02 '26 03:02

Me.Name



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!