Search code examples
c#dslfluentfluent-interface

How to build a sequence in the method calls of a fluent interface


Hi I would like to build a fluent interface for creating am object some sort of factory or builder.

I understand that I have to "return this" in order to make the methods chainable.

public class CarBuilder
{

   public  CarBuilder()
   {
       car = new Car();
   }
    private Car car;

    public CarBuilder AddEngine(IEngineBuilder engine)
    {
        car.Engine = engine.Engine();
        return this;
    }

    public CarBuilder AddWheels(IWheelsBuilder wheels)
    {
        car.Wheels = wheels.Wheels();
        return this;
    }
    public CarBuilder AddFrame(IFrameBuilder frame)
    {
        car.Frame = frame.Frame();
        return this;
    }

    public Car BuildCar()
    {
        return car;
    }

}

with this I could build a car like that:

 Car c = builder.AddFrame(fBuilder).AddWheels(wBuilder).AddEngine(eBuilder).BuildCar();

But what I need is a special sequence or workflow: I can only build the wheels on top of the frame and when the wheels exist then I'll be able to build up the engine. So instead of offering every method of the car builder I want to be able to add only the frame to the builder and then only the wheels to the frame and then the engine on top of that...

And how would it be or what would be a good implementation if the EngineBuilder itself has a fluent api like eBuilder.Cylinders(12).WithPistons()....

Would it be possible to have something like this

Car c = builder.AddFrame(fBuilder).AddWheels(wBuilder).AddEngine(x=>x.WithCylinders(12).WithPistons()).BuildCar();

So in sum how to structure the flow of the fluent interface and how to nest fluent Interfaces?


Solution

  • Instead of having a single CarBuilder class that exposes all the methods, you have multiple classes that each expose only the methods appropriate for the "state" of the car being built.

    Given your example, CarBuilder would offer only AddFrame. Instead of returning this, AddFrame returns an object of class CarBuilderWithFrame that offers only AddWheels, which returns an object of class CarBuilderWithFrameAndWheels that offers only AddEngine, which returns an object of class CarBuilderWithEverything that offers only Build.

    This example is very serial, and you can argue over whether it makes sense to go through this much work for such a linear process.

    An example I like is for printing. A Printer object could return a PrintJob object that offers three methods: Cancel, Print, or StartPage. The StartPage method would return a Page object that gives you methods for all the drawing commands one one called EndPage that gives you the PrintJob object back. This makes it easy to hardcode a simple print job.

    Printer.StartPrintJob().StartPage().DrawText("Title").DrawIcon("logo.png").EndPage().Print();