Fluent APIs - return this or new?
Asked Answered
O

4

14

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.

Offoffbroadway answered 7/11, 2013 at 16:39 Comment(8)
This is essentially asking if you should use immutable or mutable objects. Whether or not you're using a fluent interface is actually tangential to this question.Eliseelisee
@HighCore Do you hang around questions tagged with both C# and Java trying to start flame wars?Janiecejanifer
@DavidArno not really, as it is literally impossible for the dead to fight the living. BTW it is not a matter of "flame wars", but objective technical facts.Gripe
@HighCore do you have any examples of using extension methods for fluent API's?Offoffbroadway
@HighCore FYI, when you say something like "<language> doesn't support anything", then claim the obviously hyperbolic statement is an "objective, technical fact", it reflects rather poorly on you.Galvin
@Eliseelisee Well, not exactly, I would say. I'd say I'm asking if one should use mutable or immutable objects in conjunction with fluent api's. And the question arose because everywhere I google, I see only mutable fluent API's, and I ask myself - why? Maybe there's something I don't understand?Offoffbroadway
@GusRustam Really, because I'd rather say the opposite. Most any immutable object's API is inherently fluent, if it's anything other than just a data holder object. Look at string, int, IEnumerable, etc. Mutable objects on the other hand rather rarely have a fluent API. You're going to find much more fluent APIs being immutable than mutable. Regardless, my point is that you don't sit down and say, "I need to have a fluent API, should the object be immutable or mutable?" Rather you should decide, "Should this object be mutable or immutable" and then decide what its API should look like.Eliseelisee
@Eliseelisee Is seems to me, we just look from opposite points on same problem.Offoffbroadway
H
7

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.

Hetzel answered 11/11, 2013 at 14:38 Comment(2)
Interesting point. When you write "... I would imagine that numbers are being process using only one instance of the calculator, unless I decide otherwise...", you somehow contradict to answer of David Arno, who gave an example where it looks like there must be two calculators. At the same time, I guess you give the good view, a calculator should be understood (or seem to have to be understood) as a single item, which doesn't clone itself on each update. At the moment I start to think that fluent API's should be clonable and give access to cloning to the user.Offoffbroadway
Yes, I have the opposite opinion. In my view, I wouldn't expect the result be 1 when I only opened one instance of a calculator. When I open one instance of a calculator in windows and add all those values then I surely won't end up with the result 1, but 10 (1+2)*3+1. But hey, that is only my point of view. Go with the solution which makes sense to you or users of your API.Hetzel
J
4

I tend to assume fluent methods will return this. However, you raise a good point regarding mutability that caught me out when testing. Sort of using your example, I could do something like:

var calc = new Calculator(0);
var newCalc = calc.Add(1).Add(2).Mult(3);
var result = calc.Add(1);

When reading the code, I think many people would assume result would be 1 as they'd see calc + 1. Of cause with a mutable fluent system, the answer would be different as the Add(1).Add(2).Mult(3) would be applied.

Immutable fluent systems are harder to implement though, requiring more complex code. It seems a highly subjective thing as to whether the immutability benefit outweighs the work required to implement them.

Janiecejanifer answered 7/11, 2013 at 17:14 Comment(1)
I really wish there was a way to have a fluent method return a type which couldn't be assigned to a variable, but was implicitly convertible to one which could. If X holds the only reference to an object, the statement X=X.Clone() is essentially a no-op. In an expression like calc.Add(1).Add(2);, if Add(1) creates a new object and doesn't store a reference anywhere outside its return value, then Add(2) will act upon the only reference to that object anywhere in the universe, so directly mutating the object would be equivalent to making a modified copy (but much faster).Obtain
O
3

Were it not for type inference, one could "get the best of both worlds" by implementing not just an immutable FluentThing class defined in the API, but another, mutable, FluentThingInternalUseOnly which supported widening conversion to FluentThing. The Fluent members on FluentThing would construct a new instance of FluentThingInternalUseOnly and have that latter type as their return type; the members of FluentThingInternalUseOnly would operate on, and return, this.

This, if one said FluentThing newThing = oldFluentThing.WithThis(4).WithThat(3).WithOther(57);, the WithThis method would construct a new FluentThingInternalUseOnly. That same instance would be modified and returned by WithThat and WithOther; the data from it it would then be copied to a new FluentThing whose reference would be stored in newThing.

The major problem with this approach is that if someone says dim newThing = oldFluentThing.WithThis(3);, then newThing wouldn't hold a reference to an immutable FluentThing, but a mutable FluentThingInternalUseOnly, and that thing would have no way of knowing that a reference to it had been persisted.

Conceptually, what's needed is a way of having FluentThingInternalUseOnly be sufficiently public that it can be used as a return type from a public function, but not so public as to permit outside code from declaring variables of its type. Unfortunately I don't know any way to do this, though perhaps some trick involving Obsolete() tags might be possible.

Otherwise, if the objects being acted upon are complicated but the operations are simple, the best one may be able to do is have the fluent interface methods return an object which holds a reference to the object upon which it was called along with information about what should be done to that object [chaining fluent methods would effectively build a linked list] and a lazily-evaluated reference to an object upon which all appropriate changes had been applied. If one called newThing = myThing.WithBar(3).WithBoz(9).WithBam(42), a new wrapper object would be created at each step of the way, and the first attempt to use newThing as a thing would have to construct a Thing instance with three changes applied to it, but the original myThing would be untouched, and it would only be necessary to make one new instance of Thing rather than three.

Obtain answered 12/11, 2013 at 19:59 Comment(3)
Wow! Two different classes, linked list of calls, Obsolete attributes! You definitely think out of box! I will have to re-read this at least several times to get all ideasOffoffbroadway
@Rustam: I'm not sure if, the way .NET is implemented, any approach will work particularly well to "minimize astonishment", but what's needed to balance semantics and performance is a means of recognizing cases where only one reference can possibly exist to an object. If X holds the only reference to an object "George", X=X.WithY(5); and X.Y=5; are synonymous, since in either case X will refer to an instance where Y is 5, and no reference will exist to an object "George" with Y not equal to 5. In general, tracking how many references exist to an object is expensive and not worthwhile...Obtain
...but a good framework should provide assistance distinguishing between objects to which only a single in-scope reference will exist, and those whose reference may be shared. Note that fundamentally, there are two ways to encapsulate data with a reference: using a shared reference to something that will never change, or an unshared reference to something that will. In properly-written programs, there will be a clear distinction between the two paradigms--whether something is shared or unshared isn't simply a function of whether anything has shared it "yet".Obtain
E
2

I guess it would all depend on your usecase.

Most of the time when I use a Builder it is for a single-thread to manipulate mutable data. Therefore, returning this is preferred since there isn't the extra overhead and memory of returning new instances everywhere.

However, many of my Builders have a copy() method that returns a new instance with the current same values for the times when I need to support your "Pro second" use cases

Eruptive answered 7/11, 2013 at 16:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.