Progressive Interfaces

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..

9 thoughts on “Progressive Interfaces

  1. What is the difference of this approach and simply exposing setters for Name, Manufacturer etc?

  2. Jan, I gotta say: This is truly elegant. This is a nicer approach to enabling a discoverable API.

    Couple of things:

    This looks like a lot of work. I’m not saying it isn’t worth it, particularly for a public facing API. It just looks like something that can get out of hand with a larger and more substantial problem space.

    What would be interesting here is to provide the progressions in order, so that a logical chain of calls could be enforced. Methods could then be chained in a stateful way. Don’t know that this is a good idea, but interesting, eh?

    Of course, this could be done with a ridiculous chain of tiny interfaces using the method you prescribe here. Perhaps contracts in C#4 will offer something here. I haven’t looked yet.

  3. I understand hiding e.g. common system.object members but is it really necessary to hide the other API?

    I guess that simpler approach might be for eample following:

    builder usage:
    var product = ProductBuilder
    .CreateProduct()
    .Customized( product =>
    {
    product.Name = “…”;
    product.Priced = 273;
    });

    Builder code:
    public ProductBuilder Customized(Action action)
    {
    _actions.Add(action);
    return this;
    }

    the old builder method would be:
    public ProductBuilder Named (string name)
    {
    _actions.Add(product => product.Name = name);
    return this;
    }

    private Product Build()
    {
    var product = new Product();
    // apply modifications
    _actions.ForEach(action => action(product));
    return product;
    }

Comments are closed.

Proudly powered by WordPress | Theme: Code Blog by Crimson Themes.