Beginner - Fluent Interfaces

· 1 min

This continues from method chaining with C#.

To recap, Jim found a cleaner, more efficient way to build his sandwich, but he's learned the ingredients need to go on in a specific order.

Method chaining can't enforce the order methods are called in:

class SandwichFactory
{
    public SandwichFactory AddBread()
    {
        // implementation omitted for brevity
        return this;
    }

    public SandwichFactory AddCheese()
    {
        // implementation omitted for brevity
        return this;
    }
}

As sandwich construction experts, we want to enforce this order:

  1. Bread
  2. Cheese
  3. Bread

But a consumer of SandwichFactory could do this:

var createSandwich = new SandwichFactory()
    .AddCheese()
    .AddBread();

Or something worse. Let's fix it.

Coming up with a nice fluent API requires a good bit of thought. - Martin Fowler

Depending on the API you want to provide, complexity can escalate quickly. This example stays small, to show the concepts rather than a real application. Here's the solution, followed by the reasoning:

public sealed class SandwichFactory : ISandwichBread, ISandwichCheese
{
    private SandwichFactory()
    {
    }

    public static ISandwichBread Create => new SandwichFactory();

    public ISandwichCheese AddBread()
    {
        throw new NotImplementedException();
    }

    public ISandwichBread AddCheese()
    {
        throw new NotImplementedException();
    }
}

public interface ISandwichBread
{
    ISandwichCheese AddBread();
}

public interface ISandwichCheese
{
    ISandwichBread AddCheese();
}

Now a consumer of SandwichFactory can't add cheese before bread, because the interfaces only allow bread, then cheese:

SandwichFactory.Create
    .AddBread()
    .AddCheese()
    .AddBread();

Simples!