10101010101001

0101010101001

C# Fluent Interface with Type Safe Polymorphic Chain Calls

with 2 comments

Presuming you already know what fluent interfaces are I’ll skip to a certain case that caused me troubles:

Let’s say you have two Order classes: NormalOrder and SpecialOrder. Both of them inherit a base Order class. fluent_interfaces_order_class_hierarchy

I wanted to built a fluent interface for unit testing them so that I could say something like:

New.DefaultNormalOrder.AddItem(10).AddFreeShipping()   
.....
New.DefaultSpecialOrder.AddItem(15).AddLargeSpecialTaxes();

I thought it would be easy, just use the a builder pattern or extension methods in C#.
But there’s a big inconvenience with both of them that I’ll illustrate below:

Builder Problem:

    public class OrderBuilder
    {
        private Order o = new Order();

        public virtual OrderBuilder AddItem(int item)
        {
            o.AddItem(item);
            return this;
        }
    }

    public class NormalOrderBuilder : OrderBuilder
    {
        private NormalOrder no = new NormalOrder();

        public NormalOrderBuilder AddFreeShipping()
        {
            no.ShippingCost = 0;
            return this;
        }
    }

    public class SpecialOrderBuilder : OrderBuilder
    {
        private SpecialOrder so = new SpecialOrder();

        public SpecialOrderBuilder AddLargeSpecialTaxes()
        {
            so.SpecialTaxes = Int32.MaxValue;
            return this;
        }
    }

Can you spot the problem presuming that the New.DefaultNormalOrder returns a NormalOrderBuilder and that New.DefaultSpecialOrder returns a SpecialOrderBuilder?
Here it is if you try to write a small example using those builders:

//This works
New.DefaultNormalOrder.AddFreeShipping().AddItem(10);  
//This doesn't
New.DefaultNormalOrder.AddItem(10).AddFreeShipping();  

This is because AddItem is polymorphic and returns the base class OrderBuilder. Even though New.DefaultNormalOrder.AddItem(10) is actually a NormalOrderBuilder at runtime, you can’t call AddFreeShipping() because Include item returns the base OrderBuilder class breaking our chain calls :(

The same problem happens when using extension methods.

One solution would be to leave all the chain calls to the base class at the end, but that doesn’t solve the problem because there might be times when we really need to call the methods from the base class first.
Another solution would be to cast, but those won’t be fluent interfaces in my opinion :(
The fluency is gone when you have to put ugly paranthesis and casts where there’s no real need for them.
Of course you may say that all I need is override the AddItem method, call the base.AddItem and do a cast on to the NormalOrderBuilder class on the result:

    public class NormalOrderBuilder : OrderBuilder
    {
.....

        public override NormalOrderBuilder AddItem(int item)
        {
            return (NormalOrderBuilder)base.AddItem(item);
        }
    }

But this doesn’t scale well because if you have lots of methods in the base OrderBuilder class you’ll have to do this casting for all of them in all your inheritors. OK, maybe this is an exaggeration, but you’ve got to admit it’s plumbing code :)
The only solution that I’ve found is to use a combination of the Builder pattern with generics and extension methods with generics:

    public class OrderBuilder<T> where T:Order
    {
        protected internal T order = default(T);

        public OrderBuilder(T o)
        {
            order = o;
        }

        public OrderBuilder AddItem(int item)
        {
            order.AddItem(item);
            return this;
        }
    }


    public static class OrderBuilderExtensionMethods
    {
        public static OrderBuilder<T> AddFreeShipping<T>(this OrderBuilder<T> ob) where T:NormalOrder
        {
            ((NormalOrder) ob.order).ShippingCost = 0;
            return ob;
        }

        public static OrderBuilder<T> AddLargeSpecialTaxes<T>(this OrderBuilder<T> ob) where T:SpecialOrder
        {
            ((SpecialOrder) ob.order).SpecialTaxes = Int32.MaxValue;
            return ob;
        }
    }

    public class New
    {
        public static OrderBuilder<NormalOrder> DefaultNormalOrder = new OrderBuilder<NormalOrder>(new NormalOrder());
        public static OrderBuilder<SpecialOrder> DefaultSpecialOrder = new OrderBuilder<SpecialOrder>(new SpecialOrder());
    }

The compiler will enforce the constraints we specified at compile time like: “where T:NormalOrder”, so here goes the example again:

//This works
New.DefaultNormalOrder.AddFreeShipping().AddItem(10);  
//This works :D
New.DefaultNormalOrder.AddItem(10).AddFreeShipping();  
//This doesn't, the compiler screams in pain because of the generic constraints :)
New.DefaultSpecialOrder.AddItem(10).AddFreeShipping();

This is cool, because now our call chain doesn’t break and it’s type safe so we can’t make invalid call chains :D

In the end though, I’m pretty sure I wouldn’t recommend this approach because code is less readable, but it was a good exercise for generic extension methods with generic constraints.

Advertisements

Written by Liviu Trifoi

February 16, 2009 at 1:56 pm

2 Responses

Subscribe to comments with RSS.

  1. You can also do this by making the return type generic:

    # OrderBuilder base class
    public class OrderBuilder where TImplementor : OrderBuilder {
    public TImplementor AddItem(int item) { … }
    }

    # NormalOrderBuilder implementation
    public class NormalOrderBuilder : OrderBuilder {
    public AddFreeShipping() { … }
    }

    # test: works!
    New.DefaultNormalOrder.AddItem(10).AddFreeShipping();

    Pathoschild

    October 4, 2010 at 4:39 pm

    • (The previous comment broke because the blog stripped the <angle brackets>. A preview feature would be nice.)

      # OrderBuilder base class
      public class OrderBuilder<TImplementor> where TImplementor : OrderBuilder<TImplementor> {
      public TImplementor AddItem(int item) { … }
      }

      # NormalOrderBuilder implementation
      public class NormalOrderBuilder : OrderBuilder<NormalOrderBuilder> {
      public AddFreeShipping() { … }
      }

      # test: works!
      New.DefaultNormalOrder.AddItem(10).AddFreeShipping();

      Pathoschild

      October 4, 2010 at 4:42 pm


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: