About the design of a fluent interface
Now that i have presented a simple ControlStateMachine i can raise the bar a little. A statemachine that handles commands. Here is how a developer should be able to initialize this machine:
sut.WhenIn(States.Loading)
.On(Commands.Next)
.Do(() => Console.WriteLine("got next command while loading..."))
.Do(() => Console.WriteLine("doing it again..."))
.On(Commands.Previous)
.Do(() => Console.WriteLine("got previous command while loading..."));
sut.WhenIn(States.Ready)
.On(Commands.Previous)
.Do(() => Console.WriteLine("got previous command while ready..."));
So how should we define our methods to accomplish this initialization style? Let’s begin with identifying the methods we need.
- WhenIn(TSTate state)
- On(TCommand command)
- Do(Action action)
Next thing to do is analyze in which sequence these methods can be called:
| From -> To | WhenIn | On | Do |
|---|---|---|---|
| WhenIn | X | ||
| On | X | ||
| Do | X | X |
Ok, now that we have clarified the requirements a little we can start working on a solution. Let’s start with defining an interface for each of the methods:
interface IChooseState<TState, TCommand> { Q1 WhenIn(TState state); }
interface IChooseCommand<TState, TCommand> { Q2 On(TCommand command); }
interface IChooseAction<TState, TCommand> { Q3 Do(Action action); }
From WhenIn we need to be able to call On. Thus Q1 = IChooseCommand<TState, TCommand>. Q2 is also easily solved because from On we only have to be able to call Do, thus Q2 = IChooseAction<TState, TCommand>.
From Do we should be able to call both On and Do. We can do that by defining another interface which has both methods:
interface IChooseCommandAndAction<TState, TCommand> : IChooseCommand<TState, TCommand>, IChooseAction<TState, TCommand> { }
Now that we have found answers for Q1, Q2 and Q3 we can define the API for initializing our StateMachine as following:
IChooseCommand<TState, TCommand> WhenIn(TState state); IChooseAction<TState, TCommand> On(TCommand command); IChooseCommandAndAction<TState, TCommand> Do(Action action);
Now tell me about your strategy for implementing a fluent interface!
[...] that i have defined my API for initialization it is time to implement it. Inspired by the Moq.Language and Moq.Language.Flow namespaces in Moq i [...]
August 17th, 2009 at 16:46
[...] been using the concept of (Fluent) Builder classes in order to create context specifications and i blogged about the steps i take to design their API. Lately i have realised that this concept has more uses than context specifcation only. Here is an [...]
October 8th, 2009 at 08:13