Thursday, April 24, 2008

Fluent Interfaces

What is a Fluent Interface, well is basically means that you can execute a method on an object and that that object would return itself to the caller, by doing so the client can chain multiple commands to each other. This will make the code (if implemented correctly) easier to read, please take a look at the example of a Fluent Interface below:

Interface: IOrder
1
2
3
4
5
6
7
8
9
namespace FluentInterfaces
{
    public interface IOrder
    {
        IOrder ShipAddress(IAddress address);
        IOrder BillAddress(IAddress address);
        IOrder AddItem(IItem item);
    }
}
Interface: IAddress
1
2
3
4
5
6
7
8
9
namespace FluentInterfaces
{
    public interface IAddress
    {
        IAddress Street(string street);
        IAddress PostalCode(string postalCode);
        IAddress City(string city);
    }
}
Interface: IOrder
1
2
3
4
5
6
7
8
9
namespace FluentInterfaces
{
    public interface IItem
    {
        IItem ProductID(int id);
        IItem Price(double price);
        IItem NubmerOfItems(int count);
    }
}
Class: Order : IOrder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
namespace FluentInterfaces
{
    public class Order : IOrder
    {
        private IAddress _ShipAddress;
        private IAddress _BillAddress;
        private readonly List<IItem> _Items;

        public Order()
        {
            _Items = new List<IItem>();
        }

        public IOrder ShipAddress(IAddress address)
        {
            _ShipAddress = address;
            return (this);
        }
        public IOrder BillAddress(IAddress address)
        {
            _BillAddress = address;
            return (this);
        }
        public IOrder AddItem(IItem item)
        {
            _Items.Add(item);
            return (this);
        }
    }
}
Class: Address : IAddress
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
namespace FluentInterfaces
{
    public class Address : IAddress
    {
        private string _Street;
        private string _PostalCode;
        private string _City;

        public IAddress Street(string street)
        {
            _Street = street;
            return (this);
        }
        public IAddress PostalCode(string postalCode)
        {
            _PostalCode = postalCode;
            return (this);
        }
        public IAddress City(string city)
        {
            _City = city;
            return (this);
        }
    }
}
Class: Item : IItem
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
namespace FluentInterfaces
{
    public class Item : IItem
    {
        private int _Id;
        private double _Price;
        private int _Count;

        public IItem ProductID(int id)
        {
            _Id = id;
            return (this);
        }
        public IItem Price(double price)
        {
            _Price = price;
            return (this);
        }
        public IItem NubmerOfItems(int count)
        {
            _Count = count;
            return (this);
        }
    }
}
Class: Main
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
namespace FluentInterfaces
{
    class Program
    {
        static void Main(string[] args)
        {
            IOrder order = new Order()
                .ShipAddress(new Address()
                    .Street("Burggraaf 12")
                    .PostalCode("7371CW")
                    .City("Loenen"))
                .BillAddress(new Address()
                    .Street("Burggraaf 12")
                    .PostalCode("7371CW")
                    .City("Loenen"))
                .AddItem(new Item()
                    .ProductID(1)
                    .Price(15.40)
                    .NubmerOfItems(2))
                .AddItem(new Item()
                    .ProductID(132)
                    .Price(101.10)
                    .NubmerOfItems(1));
        }
    }
}
As you can see the actual creation of the Order and all of its properties has been simplified significantly and has improved readability. You can now directly see what this order is about. Indentation is only done for readability it could have been done like this:
Class: Main
1
2
3
4
5
6
7
8
9
10
namespace FluentInterfaces
{
    class Program
    {
        static void Main(string[] args)
        {
            IOrder order = new Order().ShipAddress(new Address().Street("Burggraaf 12").PostalCode("7371CW").City("Loenen")).BillAddress(new Address().Street("Burggraaf 12").PostalCode("7371CW").City("Loenen")).AddItem(new Item().ProductID(1).Price(15.40).NubmerOfItems(2)).AddItem(new Item().ProductID(132).Price(101.10).NubmerOfItems(1));
        }
    }
}
I hope you found these examples helpful. Also take a look at the following links:

http://martinfowler.com/bliki/FluentInterface.html
http://www.bofh.org.uk/articles/2005/12/21/fluent-interfaces

2 comments:

Anonymous said...

Hi Mark,

I will try not to make this comment into a "who has the best fluent interface" :) However, I feel your definition of a fluent interface is more or exactly like the definition of method chaining? What differentiate method chaining and a fluent interface for me, is readability (sentences if you want). Also that a fluent interface can make use of many other aspects than method chaining; like function sequence. Fowler has elaborated a bit more around fluent interfaces in his upcoming book. You can check it out here: http://martinfowler.com/dslwip/InternalOverview.html#FluentAndPush-buttonApis

I also had a crack at it: http://blog.torresdal.net/2008/09/02/FluentEmailInterface.aspx.

And if you're still reading ;-), check out Anders NorĂ¥s post: http://andersnoras.com/blogs/anoras/archive/2007/07/09/behind-the-scenes-of-the-planning-dsl.aspx

Mark Nijhof said...

Jon,

I agree it is a very simple example and it is Method Chaining, but the reason why I do it is to create a Fluent Interface, i.e. to make the code more readable :) I should have emphasized that a bit more. I'll update the post sometime to reflect your comments.

And whenever I do something wrong or could do better I expect readers to comment about that, it's only not happening to much since your are my second reader ;)