C# Builder pattern with inheritance

Development General

7 years ago

The Builder pattern is very helpful in case you need to encapsulate and simplify creation of a complex object. Together with the fluent interface pattern it can result in a very nice API that can be a part of your library and is immediately clear and usable for other developers. Now what if we invite inheritance to the party?

Builder: The Inherited One

Suppose we want to create a fluent builder for two inherited types of an abstract Game class - LocalGame and OnlineGame . Both types of games have some behaviors in common (like game board size, level), but each of them hase some additional setup steps, which are specific for that concrete type of game (local game my have a AI difficulty setting, on-line game may require the URL of the server we want to play on). The abstract GameBuilder could look like this:

abstract class GameBuilder
{
    protected int _boardSize = 8;
    protected int _level = 1;

    public GameBuilder BoardSize(int boardSize)
    {
        _boardSize = boardSize;
        return this;
    }

    public GameBuilder Level(int level)
    {
        _level = level;
        return this;
    }

    public abstract Game Build();
}

And its inherited versions LocalGameBuilder and OnlineGameBuilder:

class LocalGameBuilder : GameBuilder
{
    private int _aiStrength = 3;

    public LocalGameBuilder AiStrength(int aiStrength)
    {
        _aiStrength = aiStrength;
        return this;
    }

    public override Game Build() =>
        new LocalGame(_aiStrength, _boardSize, _level);
}
class OnlineGameBuilder : GameBuilder
{
    private string _serverUrl = "http://example.com/";

    public OnlineGameBuilder ServerUrl( string serverUrl)
    {
        _serverUrl = serverUrl;
        return this;
    }

    public override Game Build() =>
        new OnlineGame(_serverUrl, _boardSize, _level);
}

We can immediately see one issue with this code. The Build method, defined as abstract in the GameBuilder , returns the base Game type. This is certainly prone to errors, because the LocalGameBuilder could now produce any kind of game:

//LocalGameBuilder class
public Game Build() =>
    new OnlineGame("INVALID", _boardSize, _level); //gotcha!

We need to find a way to make the return type of the Build method strongly typed according to the specific builder.

Generic builder

Generics are part of C# since version 2.0 and are here to rescue us! We can make the abstract GameBuilder generic, so that the type returned from the Build is the concrete game type the inherited builder produces:

abstract class GameBuilder<TGame>
    where TGame : Game
{
    protected int _boardSize = 8;
    protected int _level = 1;

    public GameBuilder<TGame> BoardSize(int boardSize)
    {
        _boardSize = boardSize;
        return this;
    }

    public GameBuilder<TGame> Level(int level)
    {
        _level = level;
        return this;
    }

    public abstract TGame Build();
}

Note that we are also making sure, that the type parameter is a child of the base Game class using the where generic type constraint. The concrete builder can now just specify the type:

class LocalGameBuilder : GameBuilder<LocalGame>
{
    private int _aiStrength = 3;

    public LocalGameBuilder AiStrength(int aiStrength)
    {
        _aiStrength = aiStrength;
        return this;
    }

    //now only LocalGame can be built by LocalGameBuilder
    public override LocalGame Build() =>
        new LocalGame( _aiStrength, _boardSize, _level);
}

So far so good. Even our fluent API seems to be working well:

OnlineGame onlineGame = new OnlineGameBuilder().
    ServerUrl("http://new.com").
    Level(12).
    Build();

But everything is not peaches and cream. What if we change the order of the method calls in the chain?

OnlineGame anotherOnlineGame = new OnlineGameBuilder().
    Lavel(2).
    ServerUrl("http://myserver.com").
    Build();

We get an error on the third line - 'GameBuilder<OnlineGame>' does not contain a definition for 'ServerUrl' . The reason is that the Level method does not return OnlineGameBuilder , it returns just the plain old GameBuilder<OnlineGame> which has no knowledge of ServerUrl whatsoever.

Even more generic builder

Let's revisit the abstract builder again and try to make its fluent methods able to return its specific derived type. We make use of the so called Curiously Recurring Template Pattern which originates in C++, but we will make it usable in C#. To pass the concrete builder type to the base, we add a new type parameter, which derives from GameBuilder to GameBuilder itself:

abstract class GameBuilder<TGame, TBuilder>
    where TGame : Game
    where TBuilder : GameBuilder<TGame, TBuilder>

This is quite tricky to understand. We basically want the TBuilder type parameter to actually be an implementation of the GameBuilder which has the given game type as the first type parameter and i tself as the TBuilder type parameter. It looks really weird in the definition itself, but it becomes much more clear with usage:

class OnlineGameBuilder : GameBuilder<OnlineGame, OnlineGameBuilder>

This enables us just what we wanted - to pass the concrete builder to the abstract builder for use. Now we just have to resolve one more problem. The fluent methods need to return the instance of the class itself so that we can chain the calls:

public TBuilder BoardSize(int boardSize)
{
    _boardSize = boardSize;
    return this;
}

This does not work! On the fourth line we are trying to return this , which just an GameBuilder<TGame, TBuilder> , not the derived builder (TBuilder ). Let's help ourselves with a little hack. We will add a new abstract read-only property to the abstract GameBuilder : protected abstract TBuilder BuilderInstance { get; } This property will be implemented by the concrete builders and will allow them to return their own this instance:

class OnlineGameBuilder : GameBuilder<OnlineGame, OnlineGameBuilder>
{
    protected override OnlineGameBuilder BuilderInstance => this;
    //...
}

So now the base builder has not only access to the derived type in form of the TBuilder type parameter, but also to the instance itself by virtue of the BuilderInstance property without requiring casting. The fluent methods will now have the following form:

public TBuilder Level(int level)
{
    _level = level;
    return BuilderInstance;
}

If we now use the API to create a game, we can see that no matter which method call order you use, we will always get back the correct specific builder.

//now it works butter smooth
OnlineGame anotherOnlineGame = new OnlineGameBuilder().
    Level(2).
    ServerUrl("http://myserver.com").
    Build();

The final version

Here is the final version of our completed builders:

abstract class GameBuilder<TGame, TBuilder>
    where TGame : Game
    where TBuilder : GameBuilder<TGame, TBuilder>
{
    protected int _boardSize = 8;
    protected int _level = 1;

    protected abstract TBuilder BuilderInstance { get; }

    public TBuilder BoardSize(int boardSize)
    {
        _boardSize = boardSize;
        return BuilderInstance;
    }

    public TBuilder Level(int level)
    {
        _level = level;
        return BuilderInstance;
    }

    public abstract TGame Build();
}

class LocalGameBuilder : GameBuilder<LocalGame, LocalGameBuilder>
{
    private int _aiStrength = 3;

    protected override LocalGameBuilder BuilderInstance => this;

    public LocalGameBuilder AiStrength(int aiStrength)
    {
        _aiStrength = aiStrength;
        return this;
    }

    //now only LocalGame can be built by LocalGameBuilder
    public override LocalGame Build() =>
        new LocalGame(_aiStrength, _boardSize, _level);
}

class OnlineGameBuilder : GameBuilder<OnlineGame, OnlineGameBuilder>
{
    private string _serverUrl = "http://example.com/";

    protected override OnlineGameBuilder BuilderInstance => this;

    public OnlineGameBuilder ServerUrl(string serverUrl)
    {
        _serverUrl = serverUrl;
        return this;
    }

    public override OnlineGame Build() =>
        new OnlineGame(_serverUrl, _boardSize, _level);
}

Update: Improvement from Ondřej Kunc

There is a very nice tweak, which was pointed out to me by Ondřej Kunc in the comments of this article. Thank you for the great feedback, Ondřej! It turns out that I was unnecessarily overcomplicating the this handling with the abstract BuilderInstance property. There is a much easier way, which doesn't require the inherited builders to return their own instance (which is even a bit dangerous, because they could for instance return a new instance each time, which would destroy the builder's functionality). What we can do instead is to add a readonly field to the abstract GameBuilder and during contruction just cast this to TBuilder type and store the result in this field. We are guaranteed that this cast will succeed thanks to the generic constraints.

abstract class GameBuilder<TGame, TBuilder>
    where TGame : Game
    where TBuilder : GameBuilder<TGame, TBuilder>
{
    private readonly TBuilder _builderInstance = null;
    protected int _boardSize = 8;
    protected int _level = 1;

    public GameBuilder()
    {
        //store the concrete builder instance
        _builderInstance = (TBuilder)this;
    }

    public TBuilder BoardSize(int boardSize)
    {
        _boardSize = boardSize;
        return _builderInstance;
    }

    public TBuilder Level(int level)
    {
        _level = level;
        return _builderInstance;
    }

    public abstract TGame Build();
}

Summary

We have started out with a simple non-generic builder pattern, which was quite prone to errors. We extended it with generics, so that we can make sure that the builders do actually build the concrete types as they promise. Finally, the C# flavor of the curiously recurring template pattern helped us make the fluent API fully functional even in its generic form. You can see and get the sample source code here on my GitHub.