FailedUnitTest
FailedUnitTest

Reputation: 1800

Builder Pattern using Inheritance

I have CommonRequestBuilder and SpecificRequestBuilder, that look like this.

public class CommonRequestBuilder 
{
    protected readonly BigRequest _request;

    public CommonRequestBuilder()
    {
         _request = new BigRequest();
    }

    public CommonRequestBuilder WithExtras()
    {
        // add extra stuff to _request
        return this;
    }

    public BigRequest Build()
    {
        return _request;
    }
}

public class SpecificRequestBuilder : CommonRequestBuilder
{
    public SpecificRequestBuilder WithDetails()
    {
        // add some stuff to _request
        return this;
    }

    public BigRequest Build()
    {
        return _request;
    }
}

The issue with this pattern, is that if I use SpecificRequestBuilder like this:

_specificRequestBuilder.WithExtras().WithDetails(); // WithDetails() is not found

In above code WithDetails() cannot be resolved because I am getting the base class from WithExtras(). I can re-arrange the methods to make it work, but is there a way I can update the classes so that any order works?

Upvotes: 4

Views: 550

Answers (4)

Johnathan Barclay
Johnathan Barclay

Reputation: 20363

Personally I would implement a true polymorphic approach.

A request builder should be interfaced, then it doesn't matter what the specific type of builder is:

interface IRequestBuilder
{
    IRequestBuilder WithExtras();
    IRequestBuilder WithDetails();
    BigRequest Build();
}

You can still provide a default implementation:

public class CommonRequestBuilder : IRequestBuilder
{
    protected readonly BigRequest _request;

    public CommonRequestBuilder()
    {
         _request = new BigRequest();
    }

    public virtual IRequestBuilder WithExtras()
    {
        // add extra stuff to _request
        return this;
    }
    
    // Default implementation
    IRequestBuilder IRequestBuilder.WithDetails() => this;

    public virtual BigRequest Build()
    {
        return _request;
    }
}

And make it more specific:

public class SpecificRequestBuilder : CommonRequestBuilder
{
    public IRequestBuilder WithDetails()
    {
        // add some stuff to _request
        return this;
    }
}

Now you can use any implementation you like:

IRequestBuilder builder = new SpecificRequestBuilder().WithExtras().WithDetails();

The default implementation of WithDetails() in CommonRequestBuilder has the added benefit of not allowing that method to be called when you are dealing with a CommonRequestBuilder variable, as it does nothing.

Upvotes: 1

Michał Turczyn
Michał Turczyn

Reputation: 37377

In C# 9.0 (which is latest release, so please make sure you have it, it is shipped with VS 16.8.2) you can use covariant overrides, which would perfectly fit here. This would allow you to make such changes:

Just make method WithExtras virtual and override it in base class with return type of SpecificRequestBuilder, like below

public class CommonRequestBuilder
{
    protected readonly BigRequest _request;

    public CommonRequestBuilder()
    {
        _request = new BigRequest();
    }

    public virtual CommonRequestBuilder WithExtras()
    {
        // add extra stuff to _request
        return this;
    }

    public BigRequest Build()
    {
        return _request;
    }
}

public class SpecificRequestBuilder : CommonRequestBuilder
{
    public override SpecificRequestBuilder WithExtras()
    {
        base.WithExtras();
        return this;
    }
    public SpecificRequestBuilder WithDetails()
    {
        return this;
    }

    public BigRequest Build()
    {
        return _request;
    }
}

Now statement

_specificRequestBuilder.WithExtras().WithDetails();

becomes completely valid.

Upvotes: 2

SomeBody
SomeBody

Reputation: 8743

You'll have to use the new keyword in your SpecificRequestBuilder to have a method with a different return type:

public class SpecificRequestBuilder : CommonRequestBuilder
{
    public SpecificRequestBuilder WithDetails()
    {
        // add some stuff to _request
        return this;
    }


    public new SpecificRequestBuilder WithExtras()
    {
        return (SpecificRequestBuilder)base.WithExtras();
    }
}

Online demo: https://dotnetfiddle.net/tU4hEM

Upvotes: 2

Etienne Charland
Etienne Charland

Reputation: 4024

You can declare WithDetails on the base as a virtual method that can be overwritten.

public virtual CommonRequestBuilder WithDetails()

Build also has to be virtual on the base to be overwritten

public BigRequest Build()

You'll have the problem that WithDetails on the derived class cannot returnSpecialRequestBuilder.

I would create an interface that can be implemented by various implementations. Unless sticking to working with CommonRequestBuilder (or BaseRequestBuilder) works well enough.

public interface IRequestBuilder
{
    IRequestBuilder WithExtras();
    IRequestBuilder WithDetails();
    BigRequest Build();
}

public class CommonRequestBuilder : IRequestBuilder
{
    // ...
}

public class SpecificRequestBuilder : CommonRequestBuilder, IRequestBuilder
{
    // ...
}

Upvotes: 0

Related Questions