Blog

Progressive Interfaces

March 21, 2009

David wrote this post about a month ago where he challenged the usefulness of fluent interfaces. One of his concerns is the discoverability of a fluent API in order to determine what the correct syntax should be. Whenever you're new to a particular fluent API, it may not always be obvious what the API developer had in mind when he designed it. This can be a pain point and certainly when you don't have any examples of how the fluent API syntax should look like.

This can be improved, however, by using progressive interfaces. This is certainly nothing new and is already being used by popular fluent API's like the one provided by StructureMap.

Let's start by showing a 'traditional' expression builder like the one I provided in my previous post:

public class ProductBuilder
{
    private String Manufacturer { get; set; }
    private String Name { get; set; }
    private Double Price { get; set; }

    public static ProductBuilder CreateProduct()
    {
        return new ProductBuilder();
    }

    public ProductBuilder Named(String name)
    {
        Name = name;
        return this;
    }

    public ProductBuilder ManufacturedBy(
        String manufacturer)
    {
        Manufacturer = manufacturer;
        return this;
    }

    public ProductBuilder Priced(Double price)
    {
        Price = price;
        return this;
    }

    public static implicit operator Product(
        ProductBuilder builder)
    {
        return builder.Build();
    }

    private Product Build()
    {
        return new Product(Name, Manufacturer, Price);
    }
}

Notice that I've implemented method chaining by letting the ProductBuilder return an instance of itself. Whenever I'm using Intellisense for discovering the API of an object, I get to see all the methods provided by the expression builder.

Intellisense01

For this simple example it isn't really a problem. But imagine for a moment that we as the designers of this fluent API want our users to first provide the name, then the manufacturer and last but not least the price of a Product. In order to automatically fall into the pit of success, we can use progressive interfaces to clearly communicate our intent. First we need to create/extract the necessary interfaces that we will be using as a return type for every consecutive method call of our expression builder.

public interface IPreProductNameBuilder
{
    IPostProductNameBuilder Named(String name);
}

public interface IPostProductNameBuilder
{
    IPostProductManufacturerBuilder ManufacturedBy(
        String manufacturer);
}

public interface IPostProductManufacturerBuilder
{
    Product Priced(Double price);
}

Next step is to adjust the methods of the expression builder so that they no longer returns its the type of the expression builder itself but one of these progressive interfaces instead. Notice that the ProductBuilder class also implements every interface we just defined. This way the ProductBuilder can keep returning the instance of itself. Below is the modified code of the ProductBuilder that now uses the progressive interfaces we just defined.

public class ProductBuilder : IPreProductNameBuilder,
                              IPostProductNameBuilder,
                              IPostProductManufacturerBuilder
{
    private String Manufacturer { get; set; }
    private String Name { get; set; }
    private Double Price { get; set; }

    public static IPreProductNameBuilder CreateProduct()
    {
        return new ProductBuilder();
    }

    public IPostProductNameBuilder Named(String name)
    {
        Name = name;
        return this;
    }

    public IPostProductManufacturerBuilder ManufacturedBy(
        String manufacturer)
    {
        Manufacturer = manufacturer;
        return this;
    }

    public Product Priced(Double price)
    {
        Price = price;
        return this;
    }

    public static implicit operator Product(
        ProductBuilder builder)
    {
        return builder.Build();
    }

    private Product Build()
    {
        return new Product(Name, Manufacturer, Price);
    }
}

Now the intended syntax for the fluent API can easily be discovered by just using Intellisense.

Intellisense02

 

 

Intellisens03

 

 

Intellisens04

 

 

 

Using progressive interfaces may be slightly more work (but as always, Reshaper is your friend). The gain however is making your fluent interfaces much easier to discover by expressing your intent to the poor guy that needs to consume them..

Profile picture of Jan Van Ryswyck

Jan Van Ryswyck

Thank you for visiting my blog. I’m a professional software developer since Y2K. A blogger since Y2K+5. Curator of the Awesome Talks list. Past organizer of the European Virtual ALT.NET meetings. Thinking and learning about all kinds of technologies since forever.

Comments

About

Thank you for visiting my website. I’m a professional software developer since Y2K. A blogger since Y2K+5. Curator of the Awesome Talks list. Past organizer of the European Virtual ALT.NET meetings. Thinking and learning about all kinds of technologies since forever.

Contact information

(+32) 496 38 00 82

infonull@nullprincipal-itnull.be