Search code examples
c#javaoopdesign-patternsfluent-interface

Fluent APIs - return this or new?


I recently came up to an interesting question, what should fluent methods return? Should they change state of current object or create a brand new one with new state?

In case this short description is not very intuitive here's an (unfortunaltely) lengthy example. It is a calculator. It performs very heavy calculations and that's why he returns results via async callback:

public interface ICalculator {
    // because calcualations are too lengthy and run in separate thread
    // these methods do not return values directly, but do a callback
    // defined in IFluentParams
    void Add(); 
    void Mult();
    // ... and so on
}

So, here's a fluent interface which sets parameters and callbacks:

public interface IFluentParams {
    IFluentParams WithA(int a);
    IFluentParams WithB(int b);
    IFluentParams WithReturnMethod(Action<int> callback);
    ICalculator GetCalculator();
}

I have two interesting options for this interface implementation. I will show both of them and then I'll write what I find good and bad each of them.

So, first is a usual one, which returns this:

public class FluentThisCalc : IFluentParams {
    private int? _a;
    private int? _b;
    private Action<int> _callback;

    public IFluentParams WithA(int a) {
        _a = a;
        return this;
    }

    public IFluentParams WithB(int b) {
        _b = b;
        return this;
    }

    public IFluentParams WithReturnMethod(Action<int> callback) {
        _callback = callback;
        return this;
    }

    public ICalculator GetCalculator() {
        Validate();
        return new Calculator(_a, _b);
    }

    private void Validate() {
        if (!_a.HasValue)
            throw new ArgumentException("a");
        if (!_b.HasValue)
            throw new ArgumentException("bs");
    }
}

Second version is more complicated, it returns a new object on each change in state:

public class FluentNewCalc : IFluentParams {
    // internal structure with all data
    private struct Data {
        public int? A;
        public int? B;
        public Action<int> Callback;

        // good - data logic stays with data
        public void Validate() {
            if (!A.HasValue)
                throw new ArgumentException("a");
            if (!B.HasValue)
                throw new ArgumentException("b");
        }
    }

    private Data _data;

    public FluentNewCalc() {
    }

    // used only internally
    private FluentNewCalc(Data data) {
        _data = data;
    }

    public IFluentParams WithA(int a) {
        _data.A = a;
        return new FluentNewCalc(_data);
    }

    public IFluentParams WithB(int b) {
        _data.B = b;
        return new FluentNewCalc(_data);
    }

    public IFluentParams WithReturnMethod(Action<int> callback) {
        _data.Callback = callback;
        return new FluentNewCalc(_data);
    }

    public ICalculator GetCalculator() {
        Validate();
        return new Calculator(_data.A, _data.B);
    }

    private void Validate() {
        _data.Validate();
    }
}

How do they compare:

Pro first (this) version:

  • easier and shorter

  • commonly used

  • seems to be more memory-efficient

  • what else?

Pro second (new) version:

  • stores data in separate container, allows to separate data logic and all handling

  • allows us to easily fix part of data and then fill in other data and handle it separately. Take a look:

        var data = new FluentNewCalc()
            .WithA(1);
    
        Parallel.ForEach(new[] {1, 2, 3, 4, 5, 6, 7, 8}, b => {
            var dt = data
                .WithB(b)
                .WithReturnMethod(res => {/* some tricky actions */});
    
            // now, I have another data object for each value of b, 
            // and they have different callbacks.
            // if I were to do it with first version, I would have to create each 
            // and every data object from scratch
            var calc = dt.GetCalculator();
            calc.Add();
        });
    

What could be even better in second version?

  • I could implement WithXXX method like this:

    public IFluentParams WithXXX(int xxx) {
        var data = _data;
        data.XXX = xxx;
        return new FluentNewCalc(data);
    }
    

    and make _data readonly (i.e. immutable) which some smart people say is good.

So the question is, which way do you think is better and why? P.S. I used c# but the could well apply to java.


Solution

  • When I am trying to answer such a question in my application design I always think about what a person using my code in his application would expect.

    Take, for instace, the C# DateTime type. It is a struct and therefore immutable. When you ask for

    var today = DateTime.Now;
    var tomorrow = today.AddDays(1);
    

    what would you expect if you didn't know that DateTime is immutable? I would not expect that today is suddenly tomorrow, that would be chaos.

    As for your example, I would imagine that numbers are being processed using only one instance of the calculator, unless I decide otherwise. It makes sense, right? When I am writing an equation, I don't write each expression on a new line. I write it all together along with a result and then I jump to the next line in order to separate concerns.

    So

    var calc = new Calculator(1);
    calc.Add(1);
    calc.PrintCurrentValue(); // imaginary method for printing of a current value of equation
    

    makes perfect sense to me.