Click here to Skip to main content
15,902,189 members
Articles / Fluent

Guidelines to Fluent Interface Design in C# - Part 3

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
9 Aug 2010CPOL5 min read 11.4K   4   1
Guidelines to Fluent Interface design in C# - Part 3

This post: http://tiny.cc/37paz

In part 1, I’ve mentioned the history, the basic concepts of Fluent Interfaces and a small glossary of terms that I will use in this series of posts. In part 2, I’ve shown some way to start the sentence and gave some tips on how to implement the fluent interface. Now I will start the discussion about the methods that allow the call chaining that compose the main characteristic of the fluent interface.

Let’s remember our proposed sentence:

Place an order to John Doe with 30 yellow pencils and 20 blue pens, with a discount of 5 cents for each pen, to be delivered on December 25th of 2010, save it, and show me a summary of the order with the final price.

Now let’s define our scenario’s sample data:

C#
var products = new List<Product>(); // simulation of a repository for Products
var clients = new List<Client>();   // simulation of a repository for clients
var orders = new List<Order>();     // simulation of a repository for Orders

products.Add(new Product(1) {
	Name = "Pencil",
	Color = "Yellow",
	Price = 0.40M
});
products.Add(new Product(2) {
	Name = "Pencil",
	Color = "Red",
	Price = 0.40M
});
products.Add(new Product(3) {
	Name = "Pen",
	Color = "Blue",
	Price = 0.80M
});
products.Add(new Product(4) {
	Name = "Pen",
	Color = "Black",
	Price = 0.80M
});

clients.Add(new Client(1) { Name = "John Doe" });

In this post, we will use the most common implementation of a fluent interface method which is a method that returns the caller object. In the next post, I will show some alternative to that. Here is a sample of this method:

C#
public class Foo {
	public Foo Method1(/* parameters */) {
		/* implementation */
		return this;
	}

	public Foo Method2(/* parameters */) {
		/* implementation */
		return this;
	}
}

These methods can be called like this:

C#
var obj = new Foo();
obj.Method1(/* parameters */).Method2(/* parameters */);

Notice that in this model, there are no limitations of how many times a function can be called or the order that it's done. The following code is perfectly valid:

C#
obj.Method2(/* parameters */).Method2(/* parameters */).Method1
(/* parameters */).Method2(/* parameters */).Method1(/* parameters */).Method1(/* parameters */);

The only rule is that the returning object cannot be null or the code will throw and exception. If an exception occurs, it will be caught in the code where the method were called. If it is located in the middle of a chain, it will be a little difficult to locate the problem.

That leads to my third tip/good practice advice:

3 – Always verify the result of a chained method for null value before returning it.

Like this:

C#
public Foo Method1(/* parameters */) {
    /* implementation */
    if (this == null) throw new Exception("Method1 resulted in a null value.");
    return this;
}

This way, you will guarantee that if an error happens, you will be able to locate it inside the chain. This is particularly useful in static methods that create the subject or methods that change the resulting subject (As I said, we will come back to this in the next post.)

Now… following this implementation, we could code our proposed sentence as something like this:

C#
CreateOrder(id: 1).ToClient(named: "Jonh Doe").AddOrderItem
(product: 1, quantity:30).AddOrderItem(product: 3, quantity: 20).GiveDiscount
(item: 2, value: 0.05).ToDeliverOn(date: "25/Dec/2010").
SaveIn(orders).ShowOrderHeader().ShowOrderItem
(number: 1).ShowOrderItem(number: 2).ShowOrderTotal();

I do not recommend it.

The objective of the fluent interface is make the code readable. We should not transform the code in a single line stream of methods.

We can identify 3 basic actions:

  1. Create and set the order
  2. Save it in a repository
  3. Show the order summary

So here goes another tip/good practice advice:

4 – Avoid using a too long chain of methods. Divide the sentence in actions and use, if necessary, one chain of methods to handle each action.

Following this tip, the code will look like that:

C#
// Create the Order
var order = CreateOrder(id: 1).ToClient(named: "Jonh Doe").AddOrderItem
            (product: 1, quantity:30).AddOrderItem(product: 3, quantity: 20);
// Set optional info
order.GiveDiscount(item: 2, value: 0.05).ToDeliverOn(date: "25/Dec/2010");
// Save it
order.SaveIn(orders);
// Show output
order.ShowOrderHeader().ShowOrderItem(number: 1).ShowOrderItem(number: 2).ShowOrderTotal();

Obs.: This is just a sample! I know we could have a variable number of items in an order or suppress only call an optional setter if the value is given. But for the purpose of this post, these considerations are irrelevant.

Let’s dig a little deeper. What if we forget to call the function that sets the client which is a required information. What would happen to the code if the order item have several different properties. We can solve the first case with this simple tip.

5 – Use the method’s parameter so set required information, if the information cannot be changed, do not add a method for it.

The creator method should use the same considerations of a class constructor and the configuration methods should have the same approach as the properties’ setter methods.

Recently in C# 4.0, the methods can have optional arguments so we could replace some chained methods with optional arguments. Other languages already have this feature, for those that do not have, we need to overload the method to add optional parameters.

In our sample, the order Id is required and cannot be changed, the client name is required but can be changed and the delivery date is not required. So we could rewrite the creation and configuration methods to something like:

C#
// Create the order
var order1 = CreateOrder(id: 1, toClient: "Jonh Doe").ToDeliverOn("25/Dec/2010");
// with the "toDeliveryOn" optional argument
var order2 = CreateOrder(id: 1, toClient: "Jonh Doe", toDeliverOn: "25/Dec/2010");

Using the optional argument as a named parameter or as a chained methods depends on your criteria. But using a parameter to set required information gives strength to the code because the missing information will be validated at compilation time.

Now what about the possibility of order item having several properties in the order.

This topic actually means: Should a class handle all the properties and action of the classes that it contains?

Look at the “GiveDiscount” method. This method does not act over the order but it belongs to one of its items. So why does it have to be called from the order object? Why cannot the item have its own set of fluent methods? Look at this piece of code:

C#
// ...
order.SetItemsTo(list => {
    list.AddItem(product: 1, quantity:30);
    list.AddItem(product: 2, quantity:20).GiveDiscountOf(0.05);
});
// ...

We use a list builder method to address each item independently. First, let’s implement the item list builder:

C#
public class ItemListBuilder {
	private Order _order;

	public ItemListBuilder(Order order) {
		_order = order;
	}

	public OrderItem AddItem() {
		var item = new OrderItem(_order, _order.Items.Count);
		_order.Items.Add(item);
		return item;
	}
}

Now the method in the order that handles it:

C#
public Order SetItemsTo(this Order order, Action<ItemListBuilder> builder) {
	var items = new ItemListBuilder(order);
	builder(items);
	return order;
}

Now we have transferred the responsibility of handling the OrderItem properties to the correct class. With that in mind, we could also define a method that interact throw all order items.

C#
public Order ForEachItem(this Order order, Action<IOrderItem> action, 
Func<IOrderItem, bool> filter = null) {
	var items = filter == null ? order.Items : order.Items.Where(filter);
	foreach (var item in items) action(item);
	return order;
}

So our solution up to now would look like this:

C#
// Create the Order
var order = CreateOrder(id: 1, toClient: "Jonh Doe").SetItemsTo(list => {
    list.AddItem(product: 1, quantity:30);
    list.AddItem(product: 3, quantity: 20).GiveDiscountOf(0.05);
}).ToDeliverOn(date: "25/Dec/2010");
// Save it
order.SaveIn(orders);
// Show output
order.ShowOrderHeader().ForEachItem(i => i.ShowLine()).ShowOrderTotal();

So the last tip for this post is:

6 – As much as you can, let’s each class handles its own properties or methods. If a class contains other classes, create a method just to give access to those methods.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Pronto Solutions
Brazil Brazil
André Vianna is a Software Engineer in Rio de Janeiro, Brasil.
He is PMP, MCP and have worked as Development Project Manager, Senior Software Developer and Consultant for more than 15 years.
He worked with several major companies in Brasil, EUA, Canada and Mexico.
He has been working with the cut edge .NET platform since 2001 and is a C# specialist.

Comments and Discussions

 
QuestionPart 2 link is not working Pin
Bilal Fazlani4-Oct-15 8:32
Bilal Fazlani4-Oct-15 8:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.