In a fluent interface with “with”, is cloning expected?

  softwareengineering

22

In an object oriented language like Java or PHP (other perspectives welcome as well) if I use a fluent interface like this:

my_pizza = (new Pizza).withTopping("pineapple");
another_pizza = my_pizza.withGarlic(true);

would one expect withGarlic() to clone the pizza or would one expect my_pizza to change (into a culinary abomination)?

4

37

Looking at this code I’d have no idea. Semantically you did say it’s another pizza. But since this is of type Pizza and not a PizzaBuilder that gives you a pizza object only after you call the build method, it’s anyone’s guess how your fluent interface works.

9

27

No, the naming prefix with does not tell if it’s cloning or mutating. There are popular examples of fluent interfaces using a mutating with and some language-specific conventions (e.g. Java) that recommend with for cloning.

Moreover, An interface designed with method chaining can use either a functional approach (returning a new object each time, i.e. copy on write, no side effects) or a self-referencing approach (returning the potentially modified original object).

Fluent interface are often based on the self-referencing approach in a non-functional context. The goal is often compactness, by allowing several operations on the same object in a single expression instead of separate statements:

kitchen.order(pizzaFactory.getPizza("crusty").withTopping("x").withGarlic());

It is confusing to mix both styles and to rely on a naming convention that is not necessarily known by the larger OOP community. So whatever you chose, document it and be consistent across all the related classes.

1

12

Update based on the comments below: Just to clarify, this answer is not a judgement of whether the fluent pattern is generally good or bad, it’s about a relative trade-off in the specific situation where it is used to modify a single mutable object.


I’d suggest re-evaluating the idea of using a fluent interface in the first place if the only goal is simply to send a series of messages to the same object.

Before fluent interfaces were popular, many programs using OO languages would be full of simple ‘message passing’ code, which while slightly verbose, made its intent absolutely 100% clear in that these methods are all modifying the same object:

ButtonWidget widget = new ButtonWidget();
widget.dimensions(100, 60);
widget.backgroundColour(Colour.BLUE);
widget.text("Click Me!");

Is there anything wrong with that aside from the fact that the variable widget appears on multiple lines? There’s no ambiguity about other objects being created in memory, and having the variable there multiple times is not hurting readability at all – in fact, it’s adding useful context in making it 100% clear that all these messages are indeed passed to the same object.

Consider code which is identical in its behaviour, but using a fluent style:

ButtonWidget widget = new ButtonWidget()
    .dimensions(100, 60)
    .backgroundColour(Colour.BLUE)
    .text("Click Me!");

Maybe that took fewer keystrokes to write, and saved a couple of seconds of typing, but the cost is it not being immediately obvious to the reader whether there’s a single ButtonWidget or whether some or all of those methods returned different objects.

If there is only one object, then what has the fluent style gained over traditional OO message-passing style? And why not just go back to that traditional style which everybody already knows and recognises?

A key benefit of fluent interfaces is the way in which it promotes immutability; if you’re not going to take advantage of that, then why even bother in the first place?

6

6

I think the other answers clearly show that this is ambiguous, and when you stumble upon such code and the difference matters, you should check the implementation (or documentation, though I would personally go for the implementation first; after all, it is the ground truth, and often easier to find than the relevant docs).

As for my first expectation, I’d assume that withX would mean cloning. For mutating method, I’d rather expect a name with verb in it, like setX or addX. That is what I’d use when building a fluent interface, unless there is already a different convention in the codebase or in used libraries. It is more important to stay consistent than to use some “best naming” for a single class – the principle of least surprise is paramount.

4

Technically, the answer is “no”

Your question is

In a fluent interface with “with”, is cloning expected?

And the “technically correct” answer is that, no, “with” is not a “keyword” that implies either way.

One of two hard things

There are only two hard things in Computer Science: cache invalidation and naming things. — Phil Karlton

“Naming things” is where the problem lies. If we look at a function name and don’t know if that means one thing or the other, the solution is not to philosophize what it really means. The solution is to change the method name.

I don’t think anybody would expect an instance method called .addTopping() to clone the instance.
On the other side, a method .cloneWithTopping() also doesn’t leave a lot of room for doubt.

Cloning is a special case

Cloning is only useful for cases where you want to re-use a set of configuration options – e.g. a base_pizza with tomato sauce and cheese.

In those special cases, it’s also worth it to explicitly mention that, e.g. with an own .clone() call. After all, that kind of composition is what fluent interfaces are for, eh?

Suggestion: Use a builder

Fluent interfaces are for situations where you need to repeatedly “compose” something with many options in a “hardcoded” way. If you have that many options, all the logic for setting those options will make up quite a bit of code (the obvious assumption being that “pizza” is a simplified example).

All of that code belongs to the same responsibility “creating a pizza”, so it makes sense to put it into an own PizzaBuilder class – allowing the code in Pizza to be focused on what happens with a (completed) Pizza.

The builder pattern is well-established and doesn’t imply cloning, let alone at every step (but it wouldn’t hurt to use the more clear naming here, either).

base_pizza_builder = (new PizzaBuilder)
                   .withTomatoSauce()
                   .withCheese("mozarella");

pizza_margherita = base_pizza_builder.build();

pizza_hawaii_builder = base_pizza_builder
                   .clone()
                   .withTopping("pineapple")
                   .withTopping("ham");

pizza_hawaii = pizza_hawaii_builder.build();

11

1

I would say that many readers would expect that if they perform:

thingWithX = someThing.withX(123);
thingWithY = someThing.withY(456);
anotherWithX = someThing.withX(123);

the first call to withX would not affect the object referred to by thingWithY, and the call to withY would not affect the one referred to by anotherWithX. The easiest way to achieve such semantics would often be to have withX and withY return “near clones” of the object in question, but if someThing was immutable I don’t think that readers would (or should) necessarily expect that thingWithX and anotherWithX couldn’t compare equal to each other. Among other things, readers would/should not be astonished by an implementation where, if e.g. property X of the original object happened to be equal to 123, a withX(123) call simply returned the object upon which it was invoked).

Certainly there exist interfaces which use the preposition “with” in methods that alter the object upon which they are invoked, but I would suggest that people designing interfaces should avoid using that preposition in such cases except, perhaps, on objects which are explicitly intended for temporary usage only. While .NET doesn’t provide a practical means for a class C to export a type T in such a way that client code can call methods of C that return objects of type T, and pass those objects to other methods of C, but not otherwise store references to such objects, one could specify that certain methods’ return types are meant to be used exclusively as a chaining point for additional method calls, and perhaps give the types names that would attempt to make that point clear to anyone who sees them.

1

This will depend on the language and the conventions of your project.

In c++, we might use immutable object state and rvalue references to make it a logical copy but in actually not.

Pizza my_pizza = Pizza{}.withTopping("pineapple");
Pizza another_pizza = my_pizza.withGarlic(true).withTopping("anchovies");

the first .with would detect it was being called on an rvalue (a temporary or value about to be recycled) and modify the internal state; the second .with would know it is on an lvalue, and create an actually different object. The third would again detect it is on a temporary (rvalue) and modify itself.

With well written immutable state, even making a slightly modified duplicate object should be relatively cheap (on the order of dozens of instructions if done right).

Languages without these features (detecting temporary objects and recycling their state, and efficient immutable state tricks) are going to have to decide between “costly making useless clones” and “mutating object state in unexpected ways”, or change the interface (such as a builder, or have the caller state if they are cloning or not).

In C++23, the signatures would look like:

struct Pizza {
  Pizza withTopping(this auto&&, std::string_view);
  Pizza withGarlic(this auto&&, bool);
};

1

1

would one expect withGarlic() to clone the pizza or would one expect
my_pizza to change (into a culinary abomination)?

I am afraid, no one should expect anything that is not clearly specified in an API’s docs or requirements.

For example, a with method on an immutable object will clone an object.

On the other hand, a with method on a mutable object might either mutate the object or return a clone with the with mutation applied to it. The nature of the object with the fluent interface will determine what to expect.

In fact, two classes, A and B might implement the same fluent interface and one implement with by returning itself after a state change/transition, and the other have a with method that returns a clone.

Unless the interface docs specifies the semantics of a with method, this would be perfectly valid (though not ideal, and most certainly confusing.)

All of these possibilities are good or bad design choices depending on the context. Meaning, what are the requirements?

Therefore, we cannot expect anything from a fluent interface (or the object implementing it) unless we know the nature of the object itself.

Is the object implementing this hypothetical fluent interface of an immutable type?

Or is the object mutable? Or is the object mutable as well as a factory or composer (where the with method returns a clone rather than itself)?

Does the interface itself specifically documents a requirement on mutability or immutability (and a fluent interface by itself does not require either unless explicitly expressed)?

So, it all boils down to the question, “what kind of object this is?”

1

It depends on the conventions of the language.

In a typical statically typed object oriented language — C++, Java, C#, etc. — if I saw that code I would assume that the withX methods are mutating the self or this object. Afterall, in C++ if (new Pizza).withTopping("pineapple") was not returning the newly allocated pizza object it would be a memory leak. In C# or Java I would assume that the designer of the fluent interface would know to not be so careless with the number of temporary objects that will end up getting garbage collected to implement such a design with copies.

In a functional language I would expect the return values to be copies, if a mutating version was even possible.

That said, however, I would view such methods as being non-standardly named in an OOP context by not starting with a verb. Prepositions are not verbs. Prepositions like that would look less out of place in a functional context where mutating functions are rarer.

2

0

You need to make clear what your fluent interfaces do or don’t do, and use this consistently, if at all through your whole application. That way I can both read and write code using any fluent interface.

When applied to an instance, a method like “withGarlic” is quite similar to a setter “setGarlic”. I’d assume that withGarlic takes either the original object or a clone, applies the setter, and returns a pointer to the object. (Your objects might be immutable so the setter is private, and withGarlic must clone the object).

Would you want to use a fluent interface except when creating an object? If not then cloning is pointless and inefficient. Do you want to use it outside object creation? But why not use the setter? The fluent interface only makes sense after object creation for immutable objects.

0

This is to the designer’s discretion. There are cases where reusing the same object makes sense. There are cases where creating a duplicate and then applying the requested change to the duplicate makes more sense.

Generally speaking, the only real expectation for this type of builder pattern is that the returned object is the correct one. The only difference that not-cloning would add is that any prior variables referencing the initial object also contain this (future) change, but that is not commonly expected in builder syntax.

As there is no specific expectation that you must do one or the other, do the one that makes the most sense for your internal logic. It’ll be easier to avoid side effects by doing cloning, but it might lead to memory cleanup issues (I am bot up to speed on how effective Java’s garbage compiler is relative to C#’s).

And, of course, document your design choice in either case.

2

LEAVE A COMMENT