views:

206

answers:

1

I consider fluent interfaces very convenient for many tasks. But I feel uneasy when I end up mixing fluent methods and modifying methods in one class.

Just an example (it's a little contrived, please bear with me):

Assuming a string utility class, trimming seems good for chaining:

Str & Str::Trim() { return TrimLeft().TrimRight(); }

Other methods would naturally return a new object:

Str Str::GetFirstToken() const
{ 
   // result = first token;
   return result;
}

And there'sa 3rd type, that - by itself - would logically mutate the object and return a new one:

Str Str::SplitFirstToken() 
{ 
   result = GetFirstToken();
   // this = remainder
   return result;
}

When I use the signature that is most obvious for each method individually, I end up with these three types, and I am afraid it's not very intuitive for consuming the class which is which, especially since the return type is mroe or less the same.


I have already decided against making Str immutable - since methods like SplitToken provide core functionality. My main issue is mixing fluent methods What would you do?

  • do not use fluent methods in that interface

  • move them to a sub-interface (see below)

  • "If one is fluent, all modifying methods should be fluent"?

  • use a seocific prefix for fluent methods?

  • don't worry?

  • ???

sub interface idea:

void CStr::Trim() { TrimLeft(); TrimRight(); }
CStrFluent & Str::Fluent() { return CStrFluent(*this); }
....
str.Fluent().TrimLeft().TrimRight();

I am undecided on this, I don't really like the extra "fluent" - especially that it's a method call in C++

What do you think?

[edit] I am using "fluent" here in the basic meaning of chaining method calls on a single instance, not in the advanced sense of creating english sentences in code.

+3  A: 

I've not done much work with fluent interfaces (although I've played around with DSLs in general), but it seems to me that while the class may lend itself to this approach, it's not particularly necessary in this case. Maybe I'm missing something, but it seems to me that you are not likely to be doing a bunch of actions on a single string with no reference to anything else, which is what you've ended up with here. In addition, you're transitioning to new objects in the middle of the chain, which seems again to violate the purpose of a fluent interface, particularly when you consider what happens in this chain:

Str String = OtherString.GetFirstToken().SplitFirstToken().Trim();

I think perhaps that this type of relatively low-level utility class is the wrong place to try out a fluent interface. Fluency seems to me much more important when your objects are subject to persistent, focused attention than when they are transient and ancillary to the core logic.

Mike Burton
"you're transitioning to new objects in the middle of the chain" - exactly that's the core of my concern, well said! I'll try to swing by without the chaining for now, though firstName = line.SplitToken(...).Trim() seems a canonical thing to do.
peterchen