Coding Better: Programming From the Outside In for Fluent Interfaces. Part II

Eric Evans and Martin Fowler worked together on refining the idea of a Fluent Interface http://www.martinfowler.com/bliki/FluentInterface.html .  Most of what we'll be covering in this article is attempting to build a Fluent Interface through Method Chaining http://martinfowler.com/dslwip/MethodChaining.html .  In my last article, I discussed developing code by first writing out a sketch of how the code should be consumed first.  Next we would build out the surface are and unit tests.  Next we would write the unit tests to define the functionality we want.  Finally we implement the classes and get our unit tests to pass.

The two goals we have in building a fluent interface pattern is that we want code to be readable and we want the code to flow.  One of the tricky parts is to keep the primary objects understandable by themselves.  We need to be careful to keep classes as cohesive as possible and not build a cluttered object model.  This is why it is good to work towards separation between the "fluent" objects and our primary domain objects.

Here is a sketch of some code that I would like to build an object model to "fit" around.

Egg humptyDumpty = new Egg("Humpty Dumpty");
Wall wall = new Wall("Great wall of China");

humptyDumpty
    .Does
    .SitOn(wall)
    .HasFall(Severity.Great);

One of the things we want to keep in mind as we go is to not clutter the "Egg" object with unnecessary methods.  As a matter of fact, we'll be keeping the "Egg" object pretty clean where it just retains some state and use our Fluent Interface object to hold the methods that change the state of the Egg.  This way, the Egg class stays relatively clean and understandable by itself (nobody likes dirty eggs).

We'll take our sketch and put it in a temporary class that we'll use for building out the API.

public class Main
{
    public void Run()
    {
        Egg humptyDumpty = new Egg("Humpty Dumpty");
        Wall wall = new Wall("Great wall of China");
 
        humptyDumpty
            .Does
            .SitOn(wall)
            .HasFall(Severity.Great);
     }
}

Now I'll be using the Refactor VStudio plug-in from DevExpress to build the classes since this functionality isn't built into VStudio (yet).  Using this tool, I can right click on the not-yet-implemented class and I get an option to declare the class.

image001.gif

I'll also declare the Wall class while I'm at it.

public class Egg
{
    internal Egg(string param1)
    {
    }
}

public class Wall

{
    internal Wall(string param1)
    {
    }
}

Now we can start building out the properties and methods for each class.  This is where the Method Chaining pattern will come in handy to help us build our Fluent Interface.  The basic idea of the Method Chaining pattern is to be able to chain methods together in a way that they make sense linguistically.  The methods will pass an object along to retain state.  In this case it will be our Egg object.

An example of this method chaining is in our sketch:

humptyDumpty
    .Does
    .SitOn(wall)
    .HasFall(Severity.Great);

We'll be using this pattern when we construct the objects needed for exposing the methods.  The first thing we need is an object that serves to perform different actions on an Egg object.

image002.gif

And we change the signature a bit to expose a yet undefined class called EggActionPart.

public class Egg
{
    internal Egg(string param1)
    {
    }
 
    public EggActionPart Does
    {
        get
        {
            throw new NotImplementedException();
        }
    }
}

When we generate the EggActionPart class we need to be change the constructor so that we keep a reference to the Egg object in order to change its state from the EggActionPart.

public class EggActionPart
{
    internal EggActionPart(Egg target)
    {
    }
}

image003.gif

And change the return type of the method returns the same EggActionPart so that we can use the class to chain method calls.

public class EggActionPart
{
    internal EggActionPart(Egg target)
    {
    }
 
    public EggActionPart SitOn(Wall wall)
    {
        throw new NotImplementedException();
 
        // TODO: do stuff to egg here
 
        return this;
    }
}

So now when we generate the "HasFall()" method, it will be generated on the EggActionPart class.

image004.gif

And we'll again modify the generated method signature so we can do more to the Egg as our EggActionPart class grows.

public class EggActionPart
{
    internal EggActionPart(Egg target)
    {
    }
 
    public EggActionPart SitOn(Wall wall)
    {
        throw new NotImplementedException();
 
        // TODO: do stuff to egg here
 
        return this;
    }
 
    public EggActionPart HasFall(Severity severity)
    {
        throw new NotImplementedException();
 
        // TODO: do stuff to egg here
 
        return this;
    }
}

The EggActionPart class is a concise example of using Method Chaining to build a Fluid Interface and hopefully you can see how easy it is to generate from the outside in.  This pattern is also much easier to understand while developing our API from the outside-in.  Getting this pattern to work from the inside-out is much more difficult and more error prone.  Notice how we have kept a relatively clean separation between the Egg class which can now just retain state and the Fluent Interface object which provides us with Method Chaining.

Next we'll look at setting and retaining state in the Egg object from the EggActionPart object.  First we need a member variable in the EggActionPart object to keep track of the Egg being acted on.

public class EggActionPart
{
    internal EggActionPart(Egg target)
    {
        m_Target = target;
    }
 
    private readonly Egg
        m_Target;
 
    public EggActionPart SitOn(Wall wall)
    {
        throw new NotImplementedException();
 
        // TODO: do stuff to egg here
 
        return this;
    }
 
    public EggActionPart HasFall(Severity severity)
    {
        throw new NotImplementedException();
 
        // TODO: do stuff to egg here
 
        return this;
    }
}

Next we will have to tackle the "SitOn()" method.  We have to figure out what makes the most sense in terms of our domain.  Is it important for the Egg to keep track of what it was sitting on?  Does the wall need to know what is sitting on it?  Is it important to keep track of the Egg's posture (sitting, standing, lying down, etc)?  Since this is a fake domain we can take the liberty of just picking something, but in a real project unless it is perfectly clear, this is the point where we would probably want to talk with a domain expert and figure out what the correct modeling would be.  Now we'll start to walk down this path just a short ways so you can see how coding from the outside-in will help us determine the functional requirement of our classes before too much has been implemented.

We will say that the Wall object needs to keep track of what is on it and the Egg needs a method to enable it to sit.

public class EggActionPart
{
    internal EggActionPart(Egg target)
    {
        m_Target = target;
    }
 
    private readonly Egg
        m_Target;
 
    public EggActionPart SitOn(Wall wall)
    {
        throw new NotImplementedException();
 
        m_Target.SetState(EggState.Sitting);
        wall.AddSitter(m_Target);
 
        return this;
    }
 
    public EggActionPart HasFall(Severity severity)
    {
        throw new NotImplementedException();
 
        // TODO: do stuff to egg here
 
        return this;
    }
}

Now we need to create the EggState enum. We know that Humpty-Dumpty will most likely fall off the wall and be broken, so we'll add a state for that while we're here.

public enum EggState
{
    Sitting,
    Broken
}

Next we will look at stubbing out the Egg.SetState() and Wall.AddSitter() methods.

If the Egg falls, it is no longer sitting on the wall and we need a way to remove it.  Now the complexity of our domain is starting to show its face and we'll have to change our previous choice of how to implement the EggActionPart.SitOn() method.  We need to better handle this relation between the Egg and the Wall.  There are potentially other things that the Egg can sit on and other things that can sit on the Wall and we should probably allow for this so we'll generalize this relationship using two interfaces.  This would be an expensive change if there were lots of implementation code in place, but because we are coding from the outside-in, this will have minimal impact.

public interface ISitter
{
    Boolean IsSitting { get; }
    ISitable Seat { get; set; }
}
 
public interface ISitable
{
    void AddSitter(ISitter sitter);
    void RemoveSitter(ISitter sitter);
    ISitter[] GetSitters();
}

Now we'll make the Egg class implement the ISitter interface and stub out the methods by right clicking and selecting "Implement Interface".

image004.gif

public class Egg: ISitter
{
    internal Egg(string param1)
    {
    }
 
    public void SetState(EggState eggState)
    {
        throw new NotImplementedException();
    }
 
    public EggActionPart Does
    {
        get
        {
            throw new NotImplementedException();
        }
    }
 
    #region ISitter Members
 
    public bool IsSitting
    {
        get { throw new NotImplementedException(); }
    }
 
    public ISitable Seat
    {
        get
        {
            throw new NotImplementedException();
        }
        set
        {
            throw new NotImplementedException();
        }
    }
 
    #endregion
}

We'll do make the Wall implement ISittable in the same way:

 public class Wall:ISitable
{
    internal Wall(string param1)
    {
    }
    public void AddSitter(Egg target)
    {
        throw new NotImplementedException();
    }
 
    #region ISitable Members
 
    public void AddSitter(ISitter sitter)
    {
        throw new NotImplementedException();
    }
 
    public void RemoveSitter(ISitter sitter)
    {
        throw new NotImplementedException();
    }
 
    public ISitter[] GetSitters()
    {
        throw new NotImplementedException();
    }
 
    #endregion
}

Now we can back up and re-structure the EggActionPart.HasFall() method. Notice that I leave the NotImplementedException in place. I do this so that as I build out the unit tests I am keeping track of the methods I have implemented. This placeholder helps keep us in the TDD mindset for when we go to write tests and implementations.

public class EggActionPart
{
    internal EggActionPart(Egg target)
    {
        m_Target = target;
    }
 
    private readonly Egg
        m_Target;
 
    public EggActionPart SitOn(ISitable item)
    {
        throw new NotImplementedException();
 
        item.AddSitter(m_Target);
 
        return this;
    }
 
    public EggActionPart HasFall(Severity severity)
    {
        throw new NotImplementedException();
 
        m_Target.Seat = null; // there might be a better way to handle this
        m_Target.SetState(EggState.Broken);
 
        return this;
    }
}

This is where it is important to start putting unit tests in place to ensure that the states of the Egg object and it's relation to the Wall object are handled correctly.  As you use this approach to coding you will quickly find the places in the domain that are hiding complexity and this is the opportunity to pin it down with unit tests to ensure the final code works as expected.  I usually write more tests as I go through this process because sometimes they help me figure out if I am opening Pandora's Box by going down a certain path.  Even though it is sometimes painful, I never feel bad about going back and restructuring some of the calls amd my sketch as I go because it is far less expensive to do it now rather than when the code is done (so I'm actually quite happy because my model will be that much better).  If I can find a simpler way to express something with the way I am structuring the classes it is always worth changing.  If you do the same, you'll thank yourself a few iterations down the road.

Hopefully you can see how once we start digging in a bit something seemingly simple it can grow in complexity rather quickly and how if we code from the outside-in these changes can be handled early in the coding cycle rather than after there is a bunch of implementation code in place and time has been wasted on writing code that has to be thrown out or heavily refactored. In my next article, we'll look at building out a bit more complex surface are that will take some more thought and a few more tricks.

Until then,
Happy coding


Similar Articles