The problem with this is that you change the return type of the method in a non-obvious way. The method's return type is Conf
, but in reality, it doesn't actually return a useful value at all. Someone who looks at the API documentation might then wonder why the method returns Conf
, when in reality, the method has only a side-effect and no useful return.
Which brings me to the second problem: this only works if the methods have side-effects and no useful return value. But, you should strive to write purely functional, referentially-transparent methods without side-effects, so this promotes bad design.
The first problem can be alleviated in a language with a precise enough type system. For example, in Scala, you could make the return type the singleton type this.type
, which means that the method returns this
and only this
(it cannot return a different object of the same type as this
, it can only return this
itself). So, you would define your methods like this:
class Conf {
def +=(coll: Collection): this.type = {
// do stuff with `coll`
this
}
def +=(rule: Rule): this.type = {
// do stuff with `rule`
this
}
def +=(event: Event): this.type = {
// do stuff with `event`
this
}
}
and use it like this:
conf += someCollection += someRule += someEvent
[Note: I chose an overloaded method for the different types, you could obviously use different names instead, if you want, then it would be something like conf addCollection someCollection addRule someRule registerEvent someEvent
.]
This still leaves the second problem, however: it only makes sense with side-effects.
Many languages have message cascades that allow you to send multiple messages to the same receiver, e.g. in Smalltalk, your example could be written as:
conf
addCollection: aCollection;
addRule: aRule;
registerEvent: anEvent.
So, there is simply no need for message chaining in this way.
In Ruby, you could (ab)use BasicObject#instance_eval
, although I would recommend against it:
conf.instance_eval do
add_collection a_collection
add_rule a_rule
register_event an_event
end
I would mostly avoid doing this, since it implies side-effects and side-effects should be avoided in general, unless absolutely necessary. If you do this, it should be clearly documented, ideally via the type system.
Note: message chaining where every message in the chain returns a new object and is purely functional and referentially-transparent without side-effects, that is a whole different matter. As long as it doesn't violate the Law of Demeter for Functions, that is perfectly okay.
In a Fluent DSL, where it is clear that the return types of methods don't make sense in isolation and should only be understood as part of the DSL structure as a whole, the situation is different. But, in a Fluent DSL, you would typically return a new builder object every time anyway, to help with type inference, intelligent code completion, etc. I gave an example of this in this answer to a question about Fluent DSLs (more precisely, Fluent Builders).