Beginner - Fluent Interfaces

Let us define a simple fluent interface to allow smart method chaining, continued from Method Chaining

So, let’s continue from our last post method chaining with C#!

To recap, Jim ๐Ÿง” found a cleaner, more efficient method of building his sandwich, however he’s discovered there should be a specific order to the ingredients.

With method chaining we can’t enforce a specific order of methods called. Let’s illustrate this problem.

    class SandwichFactory
    {

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

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

    }

We, as sandwich construction experts, want to enforce the following pattern

  • ๐Ÿž Bread
  • ๐Ÿง€ Cheese
  • ๐Ÿž Bread

However, a consumers of our SandwichFactory class could do the following:

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

Or something worse! Let’s fix this problem.

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

Depending on the API we want to provide, complexity can rapidly escalate. For our example, we’re going to keep this small to illustrate the concepts more than application. Lets provide our solution below and go through the reasoning behind decisions made in regards to fluent interfaces:

        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();
        }
  • The constructor needs to be private - We don’t want a direct instance of our class to be created, since this would provide access to the AddBread and AddCheese methods (which is exactly what we’re trying to avoid)!
  • Static Entry point - our entry point, in this case Create should return the interface we want to start with which is ISandwichBread and an instance of our class
  • Keep parameters low - we don’t want to use tons of parameters per method, in our example we’re using 0, but it’s something to be mindful of

Consuming the SandwichFactory API, we now can’t add cheese ๐Ÿง€ before bread ๐Ÿž, since our design enforces a bread > cheese hierarchy.

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

Simples! ๐Ÿ’ช

Ash Grennan
Ash Grennan
Snr Software Engineer

Deliver value first, empower teams to make technical decisions, allow ownership, slice features thinly, enable and encourage constant value delivery through working software. Works @ AO.com, hold a BSc & MSc in software engineering, certified AWS Solutions Architect (more on LinkedIn). A fan of Serverless computing, distributed systems, and anything published by serverless.com ๐Ÿงก