Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inheritance between builders - how to deal with types?

I have this builder in C# (naturally, the example is simplified):

class BusBuilder
{
    Wheels mWheels = DefaultWheels;
    int mRoute = 0;

    public BusBuilder WithWheels(Wheels aWheels)
    {
        mWheels = aWheels;
        return this;
    }

    public BusBuilder WithRoute(int aRoute)
    {
        mRoute = aRoute;
        return this;
    }

    public Bus Build()
    {
        return new Bus { Wheels = mWheels, Route = mRoute };
    }
}

It's used like this:

Bus bus = 
    new BusBuilder()
    .WithWheels(someWheels)
    .WithRoute(50)
    .Build()

Now I want to extract a superclass that contains only some of the methods:

class VehicleBuilder
{
    Wheels mWheels = DefaultWheels;

    public VehicleBuilder WithWheels(Wheels aWheels)
    {
        mWheels = aWheels;
        return this;
    }
}

class BusBuilder : VehicleBuilder
{
    ...
}

The problem is that now I can't write

Bus bus = 
    new BusBuilder()
    .WithWheels(someWheels)
    .WithRoute(50)
    .Build()

because WithWheels returns a VehicleBuilder rather than a BusBuilder, and therefore does not define a WithRoute method.

How would you design this?

like image 608
Ilya Kogan Avatar asked Dec 08 '22 18:12

Ilya Kogan


1 Answers

The builder pattern is a bit of a pain when it comes to inheritance. You can do something like this:

class VehicleBuilder<T> where T : VehicleBuilder<T>
{
    private T @this;

    protected VehicleBuilder()
    {
        // Or pass it in as a constructor parameter
        @this = (T) this;
    }

    public T WithWheels(...)
    {
        return @this;
    }
}

Then:

class BusBuilder : VehicleBuilder<BusBuilder>
{
    ...
}

At that point, your WithWheels method will still return a BusBuilder so you can still call WithRoute.

You'll also need a new Build method in each derived class builder, mind you...

like image 134
Jon Skeet Avatar answered Dec 11 '22 07:12

Jon Skeet