Are fluent interfaces a violation of the Command Query Separation Principle?
Asked Answered
E

5

7

I started writing a fluent interface and took a look at an older piece Martin Fowler wrote on fluent interfaces (which I didn't realize he and Eric Evans coined the term). In the piece, Martin mentions that setters usually return an instance of the object being configured or worked on, which he says is a violation of CQS.

The common convention in the curly brace world is that modifier methods are void, which I like because it follows the principle of CommandQuerySeparation. This convention does get in the way of a fluent interface, so I'm inclined to suspend the convention for this case.

So if my fluent interface does something like:

myObject
  .useRepository("Stuff")
  .withTransactionSupport()
    .retries(3)
  .logWarnings()
  .logErrors();

Is this truly a violation of CQS?

UPDATE I broke out my sample to show logging warnings and errors as separate behaviors.

Euphonium answered 16/3, 2012 at 18:0 Comment(2)
Does logWarningsAndErrors return anything? If not, then is it really a fluent interface?Tetrastichous
@Tetrastichous updated my sample. Sure, logWarningsAndErrors returns an interface that I can add additional behaviors to.Euphonium
J
0

No. The pattern here is "Configuration". Such configuration commands return the configuration object itself as opposite to something unrelated to the command. A violation of the Command/Query segregation would occur if the commands which serve the configuration purpose returned some unrelated data, for example:

if (myObject.UseRepository("Stuff") > 1 && myObject.UseRepository("Bla") < 5) {
    // oh, good, some invisible stuff internal to myObject is in right interval...
}
Jovanjove answered 16/3, 2012 at 18:8 Comment(1)
sry, i don't understand your answer, but i want to. can you please expand it? "if you had..." had where? also, there is no altering of internal state in your snippet. i see the violation of "tell, don't ask" principle, but that's itFess
I
9

Yes, it is. All those methods are obviously returning something, and equally obviously they have side effects (judging from the fact that you don't do anything with the return value, yet you do bother to call them). Since the definition of CQS states that mutators should not return a value we have a clear-cut violation in our hands.

But does it matter to you that CQS is violated? If the fluent interface makes you more productive all things considered, and if you consider it a well-known pattern with equally well-known benefits and drawbacks, why should it matter that it violates principle X on paper?

Ish answered 16/3, 2012 at 18:6 Comment(3)
How is it obvious that logWarningsAndErrors returns something?Tetrastichous
@M.Babcock: Technically it is not, just from that snippet. You got me there.Ish
Agree. Why does it matter if it violets principle X, if it helps you use it.Monongahela
G
3

It violates this principle when it changes objects but not when it only returns a new object.

var newObject = myObject
    .useRepository("Stuff")
    .withTransactionSupport()
    .retries(3)
    .logWarningsAndErrors(); 

If myObject is unchanged after this statement, everything is OK. Generally spoken, a fluent interface violates the CQS principle, if, and only if it has side effects.

However the question is, if your example does represent a query at all. Does "fluent" necessarily mean "query"? It could probably just be perceived as an action-fluent-interface where the same object is passed from one action to the next.

Gamosepalous answered 16/3, 2012 at 18:7 Comment(3)
hm, but the whole point of configuration is to change the object (subject to configuration) so why bother?Jovanjove
What difference does it make if object being modified is a new or an old instance? Any one of those 4 calls either modifies the return value of the expression (whatever that is) or does nothing and so should be omitted.Ish
The question is, if it does modify an existing object and return a result at the same time, making it a command and a query at the same time. It should be either a command or a query.Gamosepalous
J
0

No. The pattern here is "Configuration". Such configuration commands return the configuration object itself as opposite to something unrelated to the command. A violation of the Command/Query segregation would occur if the commands which serve the configuration purpose returned some unrelated data, for example:

if (myObject.UseRepository("Stuff") > 1 && myObject.UseRepository("Bla") < 5) {
    // oh, good, some invisible stuff internal to myObject is in right interval...
}
Jovanjove answered 16/3, 2012 at 18:8 Comment(1)
sry, i don't understand your answer, but i want to. can you please expand it? "if you had..." had where? also, there is no altering of internal state in your snippet. i see the violation of "tell, don't ask" principle, but that's itFess
G
0

I think it depends on what those methods are doing. If each one is it's own command, then yes, it could be breaking CQS.

However, you could fix this easily 2 different ways.

  1. Just don't chain the commands. Just do myObject.useRepository(".."). Then call the next one, etc. But if the next item in the chain requires information from the previous one you would be in trouble.

  2. Instead of making each of these their own command, instead these chained things are simply updating data on the DTO directly. Then at the end, you run a method called .Configure() that then sends this DTO to a single command that does all of the processing.

Gandzha answered 18/5, 2020 at 18:42 Comment(0)
M
0

If we ignore the type system DSL design, fluent interface is exactly the same as method chaining.

o.A().B() is equivalent to a = o.A(); a.B()

It does violate command-query separation in a mutable data structure. We have to explicitly add superfluous return this in method implementation (Here a & o refer to the same object) (By the way I prefer method cascading in such case)

However, we also often see it in immutable data structure, because a pure function has to return the result. (Here a & o refer to different objects`) In this case it does not violate command-query separation

Mclyman answered 22/4, 2022 at 0:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.