55

Having chaining implemented on beans is very handy: no need for overloading constructors, mega constructors, factories, and gives you increased readability. I can't think of any downsides, unless you want your object to be immutable, in which case it would not have any setters anyway. So is there a reason why this isn't an OOP convention?

public class DTO {

    private String foo;
    private String bar;

    public String getFoo() {
         return foo;
    }

    public String getBar() {
        return bar;
    }

    public DTO setFoo(String foo) {
        this.foo = foo;
        return this;
    }

    public DTO setBar(String bar) {
        this.bar = bar;
        return this;
    }

}

//...//

DTO dto = new DTO().setFoo("foo").setBar("bar");
muru
  • 142
  • 8
Ben
  • 1,002
  • 35
    Because Java may be the only language where setters aren't an abomination unto man... – Telastyn Feb 02 '16 at 17:43
  • 13
    It's bad style because the return value is not semantically meaningful. It's misleading. The only upside is saving very few keystrokes. – usr Feb 02 '16 at 20:12
  • 65
    This pattern is not uncommon at all. There is even a name for it. It's called fluent interface. – Philipp Feb 02 '16 at 22:31
  • 9
    You can also abstract this creation in a builder for a more readable result. myCustomDTO = DTOBuilder.defaultDTO().withFoo("foo").withBar("bar").Build(); I'd do that, so as to not conflict with the general idea that setters are voids. –  Feb 02 '16 at 23:21
  • 10
    @Philipp, while technically you're right, wouldn't say that new Foo().setBar('bar').setBaz('baz') feels very "fluent". I mean, sure it could be implemented exactly the same way, but I'd very much expect to read something more like Foo().barsThe('bar').withThe('baz').andQuuxes('the quux') – Wayne Werner Feb 03 '16 at 04:00
  • 2
    @Telastyn can you elaborate on that? I'm struggling to see what Java setters do that is so special. – Gusdor Feb 03 '16 at 08:26
  • 4
    This is not the definition of fluent. You can have a clean fluent interface if you return a new and immutable object. – usr Feb 03 '16 at 11:47
  • 1
    @gusdor - most languages consider setters on objects (as opposed to Plain Old Data) to be a smell, since it is directly manipulating class state, violating encapsulation/invariants. In languages like C#, setters like this are not idiomatic, since they use prop = value sort of syntax, which cannot chain. – Telastyn Feb 03 '16 at 12:56
  • If something weird happens on the chained method line, I will need to debug-jump through all of the methods in order until I find the one who dun' it. – Ceiling Gecko Feb 03 '16 at 14:02
  • 3
    +1 for @AlexM.'s suggestion for a builder. It gives the possibility of an immutable object with most of the flexibility of setters. – Chad Schouggins Feb 03 '16 at 19:35
  • The reason I don't like it, is that you're doing two things: changing your object and as a function: returning an instance of this object. Also you example smells a lot like a constructor, but that depends whether setting foo and bar is optional, because if you don't chain your foo and bar will be null, which may or may not be good. If they're required to be set, this is a bad way. – Pieter B Feb 03 '16 at 22:08
  • @AlexM. It is something that can be solved in Java as follows: public class Foo<T extends Foo> {...} with the setter returning Foo<T>, that way Inheritance doesn't break the setters. Also those 'setter' methods, I also prefer to call them 'with' methods. If overloading works fine, then just Foo<T> with(Bar b) {...}, otherwise Foo<T> withBar(Bar b). – YoYo Feb 04 '16 at 09:57
  • 1
    @CeilingGecko On debug-jumping - assuming the only difference was the return value of the setters (void vs DTO), don't you still have to go through them anyway, just laid out in separate statements? – Lawrence Feb 04 '16 at 11:18
  • @Lawrence if one statement decides to throw a nasty exception the stacktrace points to the real offender since there is only 1 thing done on the line – masterX244 Feb 04 '16 at 14:12
  • 1
    @masterX244 I've just tried new X().setA().setB().setC(); in Java with a dummy class X, setA and setC returning X, and setB throwing an exception. The stack trace points to setB. In general, in a chain of function calls with one of them throwing an exception, I'd expect the stack trace to identify that as the 'leaf node' that threw the exception. – Lawrence Feb 04 '16 at 14:21
  • 1
    This pattern is hardly non-OOP. In Smalltalk (OO language written before there ever was a Java),having every instance method return self was standard practice. – PaulMcG Feb 04 '16 at 14:54
  • Nothing forbids you from writing extra constructors or static factories to help you creating value objects. – Olivier Grégoire Feb 04 '16 at 15:13
  • In Swift you don’t use setter methods, but properties and what looks like an assignment operator. Chaining is not syntactically possible. – gnasher729 Aug 21 '22 at 14:34
  • @Telastyn isn't the opposite true? Setters are functions which do not manipulate instance state directly; they can apply logic, refuse e.g. IllegalArgumentException and so on. Direct member access on a POJO would be the smell. Did you mix both up, or is there some aspect I didn't get? – foo Feb 04 '23 at 17:23
  • @foo - look at the OP’s example, they are definitely manipulating state directly. Setters as I am using the term is anything like SetFoo. By definition, you’re changing the state of Foo… – Telastyn Feb 04 '23 at 23:22
  • @Telastyn - I see the OP doing the Bad Thing, but (a) it's short sample code, (b) doesn't mean it has to be that way. It can be made safe. Whereas direct access to a POJO member never can be made safe. – foo Feb 06 '23 at 15:30
  • @foo - it’s a pojo. If you aren’t providing direct access, then it by definition isn’t a plain old object. – Telastyn Feb 06 '23 at 15:35

7 Answers7

55

So is there a reason why isn't this a OOP convention?

My best guess: because it violates CQS

You've got a command (changing the state of the object) and a query (returning a copy of state -- in this case, the object itself) mixed into the same method. That's not necessarily a problem, but it does violate some of the basic guidelines.

For instance, in C++, std::stack::pop() is a command that returns void, and std::stack::top() is a query that returns a reference to the top element in the stack. Classically, you would like to combine the two, but you can't do that and be exception safe. (Not a problem in Java, because the assignment operator in Java doesn't throw).

If DTO were a value type, you might achieve a similar end with

public DTO setFoo(String foo) {
    return new DTO(foo, this.bar);
}

public DTO setBar(String bar) {
    return new DTO(this.foo, bar);
}

Also, chaining return values are a colossal pain-in-the- when you are dealing with inheritance. See the "Curiously recurring template pattern"

Finally, there's the issue that the default constructor should leave you with an object that is in a valid state. If you must run a bunch of commands to restore the object to a valid state, something has gone Very Wrong.

  • 5
    Finally, a real catain here. Inheritance is the real problem. I think chaining could be a conventional setter for data-representation-objects that are used in composition. – Ben Feb 02 '16 at 17:56
  • 7
    "returning a copy of state from an object" - it doesn't do that. – user253751 Feb 02 '16 at 20:21
  • @Benedictus inheritance is not a problem if this is done correctly, but it does add complexity. You can use the getSelf() trick... – Boris the Spider Feb 02 '16 at 21:46
  • 2
    I would argue that the biggest reason for following those guidelines (with some notable exceptions like iterators) is that it makes the code vastly easier to reason about. – jpmc26 Feb 02 '16 at 22:27
  • @BoristheSpider: what is this getSelf() thing? Is this specific to a particular language? – Matthieu M. Feb 03 '16 at 10:39
  • 5
    @MatthieuM. nope. I mainly speak Java, and it's prevalent there in advanced "fluid" APIs - I'm sure it also exists in other languages. Essentially you make your type generic on a type extending itself. You then add an abstract T getSelf() method with returns the generic type. Now, instead of returning this from your setters, you return getSelf() and then any overriding class simply makes the type generic in itself and returns this from getSelf. This way the setters return the actual type and not the declaring type. – Boris the Spider Feb 03 '16 at 10:42
  • @BoristheSpider: I don't see how it would translate in C++ or Rust... sounds quite specific :x – Matthieu M. Feb 03 '16 at 10:44
  • 1
    @MatthieuM. really? Sounds similar to CRTP for C++ ... – Useless Feb 03 '16 at 12:45
  • @Useless: Ah, it might. Given that we were talking OO I was expecting virtual methods, in which case the derived class can use a more distinct return type (covariance) however the if you have a reference to the base class you cannot call any derived class setter and I was wondering how Java would make it possible :) – Matthieu M. Feb 03 '16 at 13:37
  • 2
    For the record, the reason why std::stack::pop doesn't return the value that popped is because the function is older than move references so it would necessarily have to make a copy to return it, which could be too expensive it you don't plan to use the value. A possible solution would be creating two separate functions, std::stack::pop and std::stack::pop_noreturn, but keeping pop as void and top as returning a value is good enough. – Martín Fixman Feb 03 '16 at 18:32
  • @MatthieuM. the return type of the setter isn't the the Object it's the generic parameter. i.e. the extending class can specify the return type of the setter - similar to how a class inheriting from a List of a specific type would work - it can tell the parent class what the type of get(int idx) will be. – Boris the Spider Feb 04 '16 at 14:37
  • @BoristheSpider: Okay, so that's something CRTP could do in C++ indeed T& set(...) { ...; return static_cast<T&>(*this); }. – Matthieu M. Feb 04 '16 at 17:50
  • 1
    @MatthieuM. that would be the "non type-safe" way of doing it - I think. Usually one would have a method &T getSelf() that would be virtual (is that the right term - implemented in the subclass). The setter would return getSelf() and the subclass would then return this. As this subclass knows its own type - this would be safer. But however you do it, fluent APIs don't have to be broken by inheritance. – Boris the Spider Feb 04 '16 at 17:52
  • @BoristheSpider: In C++, T& getSelf() cannot be virtual (you cannot mix compile-time and run-time polymorphism). Also, static_cast checks at compile-time that T inherits from your base, which also helps; you can eliminate the risk of error by using dynamic_cast (but it has a performance penalty). – Matthieu M. Feb 04 '16 at 18:00
  • 1
    "You've got a command (changing the state of the object) and a query (returning a copy of state -- in this case, the object itself) mixed into the same method.": If you were using functional objects, a setter would return a new object with one attribute changed. Then a fluent interface like this would be perfectly OK. – Giorgio Feb 04 '16 at 20:27
  • I agree and feel any discussion about fluid interfaces should be clear about whether it is dealing with immutable or mutable objects, because the latter doesn't work so well in my opinion. – LegendLength Jul 15 '17 at 10:33
36
  1. Saving a few keystrokes isn't compelling. It might be nice, but OOP conventions care more about concepts and structures, not keystrokes.

  2. The return value is meaningless.

  3. Even more than being meaningless, the return value is misleading, since users may expect the return value to have meaning. They may expect that it is an "immutable setter"

    public FooHolder {
        public FooHolder withFoo(int foo) {
            /* return a modified COPY of this FooHolder instance */
        }
    }
    

    In reality your setter mutates the object.

  4. It doesn't work well with inheritance.

    public FooHolder {
        public FooHolder setFoo(int foo) {
            ...
        }
    }
    
    public BarHolder extends FooHolder {
        public FooHolder setBar(int bar) {
            ...
        }
    } 
    

    I can write

    new BarHolder().setBar(2).setFoo(1)
    

    but not

    new BarHolder().setFoo(1).setBar(2)
    

For me, #1 through #3 are the important ones. Well-written code is not about pleasantly arranged text. Well-written code is about the fundamental concepts, relationships, and structure. Text is only a outward reflection of code's true meaning.

Paul Draper
  • 6,012
  • 3
    Most of these arguments apply to any fluent/chainable interface, though. – Casey Feb 02 '16 at 22:29
  • @Casey, yes. Builders (i.e. with setters) is the most case I see of chaining. – Paul Draper Feb 02 '16 at 23:33
  • 13
    If you're familiar with the idea of a fluent interface, #3 doesn't apply, since you're likely to suspect a fluent interface from the return type. I also have to disagree with "Well-written code is not about pleasantly arranged text". That's not all it's about, but nicely arranged text is rather important, since it allows the human reader of the program to understand it with less effort. – Michael Shaw Feb 03 '16 at 05:36
  • 3
    Setter chaining is not about pleasantly arranging the text or about saving keystrokes. It's about reducing the number of identifiers. With setter chaining you can create and set up the object in a single expression, which means you might not have to save it in a variable - a variable you'll have to name, and that will stay there until the end of the scope. – Idan Arye Feb 03 '16 at 13:00
  • @MichaelShaw, yes, if chaining is a convention you're used to, then you'll understand it, by definition. But it still demands some additional time for understanding. For example, GetObjectRequest#withBucketName returns a GetObjectRequest. Is it a new one or a modified one? Conventions are insufficient. Time to look at the docs. – Paul Draper Feb 03 '16 at 13:18
  • @IdanArye, in most languages, you can create arbitrary scopes. E.g. in Java, with {}. Naming things is not foreign to OOP. Naming classes, naming methods, naming parameters. – Paul Draper Feb 03 '16 at 13:21
  • 4
    About point #4, it is something that can be solved in Java as follows: public class Foo<T extends Foo> {...} with the setter returning Foo<T>. Also those 'setter' methods, I prefer to call them 'with' methods. If overloading works fine, then just Foo<T> with(Bar b) {...}, otherwise Foo<T> withBar(Bar b). – YoYo Feb 04 '16 at 09:51
12

I don't think this is an OOP convention, it's more related to the language design and its conventions.

It seems you like to use Java. Java has a JavaBeans specification which specifies the return type of the setter to be void, i.e. it is in conflict with chaining of setters. This spec is widely accepted and implemented in a variety of tools.

Of course you might ask, why isn't chaining part of the specification. I don't know the answer, maybe this pattern just wasn't known/popular at that time.

qbd
  • 2,906
  • Yeah, JavaBeans is exactly what I had in mind. – Ben Feb 02 '16 at 17:21
  • I don't think that most applications that use JavaBeans care, but they might (using reflection to grab the methods that are named 'set...', have a single parameter, and are void return). Many static analysis programs complain about not checking a return value from a method that returns something and this could be very annoying. –  Feb 02 '16 at 17:33
  • @MichaelT reflective calls to get methods in Java do not specify return type. Calling a method that way returns Object, which may result in the singleton of type Void being returned if the method specifies void as its return type. In other news, apparently "leaving a comment but not voting" is grounds for failing a "first post" audit even if it is a good comment. I blame shog for this. –  Feb 24 '16 at 04:43
8

As other people have said, this is often called a fluent interface.

Normally setters are call passing in variables in response to the logic code in an application; your DTO class is a example of this. Conventional code when setters don’t return anything is normal best for this. Other answers have explained way.

However there are a few cases where fluent interface may be a good solution, these have in common.

  • Constants are mostly passed to the setters
  • Program logic does not change what is passed to the setters.

Setting up configuration, for example fluent-nhibernate

Id(x => x.Id);
Map(x => x.Name)
   .Length(16)
   .Not.Nullable();
HasMany(x => x.Staff)
   .Inverse()
   .Cascade.All();
HasManyToMany(x => x.Products)
   .Cascade.All()
   .Table("StoreProduct");

Setting up test data in unit tests, using special TestDataBulderClasses (Object Mothers)

members = MemberBuilder.CreateList(4)
    .TheFirst(1).With(b => b.WithFirstName("Rob"))
    .TheNext(2).With(b => b.WithFirstName("Poya"))
    .TheNext(1).With(b => b.WithFirstName("Matt"))
    .BuildList(); // Note the "build" method sets everything else to
                  // senible default values so a test only need to define 
                  // what it care about, even if for example a member 
                  // MUST have MembershipId  set

However creating good fluent interface is very hard, so it only worth it when you have lots of “static” setup. Also fluent interface should not be mixed in with “normal” classes; hence the builder pattern is often used.

Glorfindel
  • 3,137
Ian
  • 4,603
5

I think much of the reason it's not a convention to chain one setter after another is because for those cases it's more typical to see an options object or parameters in a constructor. C# has an initializer syntax as well.

Instead of:

DTO dto = new DTO().setFoo("foo").setBar("bar");

One might write:

(in JS)

var dto = new DTO({foo: "foo", bar: "bar"});

(in C#)

DTO dto = new DTO{Foo = "foo", Bar = "bar"};

(in Java)

DTO dto = new DTO("foo", "bar");

setFoo and setBar are then no longer needed for initialization, and can be used for mutation later.

While chainability is useful in some circumstances, it's important to not try to stuff everything on a single line just for the sake of reducing newline characters.

For example

dto.setFoo("foo").setBar("fizz").setFizz("bar").setBuzz("buzz");

makes it harder to read and understand what's happening. Reformatting to:

dto.setFoo("foo")
    .setBar("fizz")
    .setFizz("bar")
    .setBuzz("buzz");

Is much easier to understand, and makes the "mistake" in the first version more obvious. Once you've refactored code to that format, there's no real advantage over:

dto.setFoo("foo");
dto.setBar("bar");
dto.setFizz("fizz");
dto.setBuzz("buzz");
zzzzBov
  • 5,824
  • 1
  • 28
  • 28
  • 3
  • I really cannot agree with the readability issue, adding instance before each call is not making it clearer at all. 2. The initialisation patterns you showed with js and C# has nothing to do with constructors: what you did in js is passed a single argument, and what you did in C# is a syntax shugar that calls geters and setters behind the scenes, and java does not have the geter-setter shugar like C# does.
  • – Ben Feb 02 '16 at 17:48
  • 1
    @Benedictus, I was showing various different ways that OOP languages handle this issue without chaining. The point wasn't to provide identical code, the point was to show alternatives that make chaining unnecessary. – zzzzBov Feb 02 '16 at 18:42
  • 2
    "adding instance before each call is not making it clearer at all" I never claimed that adding the instance before each call made anything clearer, I just said that it was relatively equivalent. – zzzzBov Feb 02 '16 at 18:43
  • 1
    VB.NET also has a usable "With" keyword which creates a compiler-temporary reference so that e.g. [using / to represents line break] With Foo(1234) / .x = 23 / .y = 47 would be equivalent to Dim temp=Foo(1234) / temp.x = 23 / temp.y = 47. Such syntax does not creating ambiguity since .x by itself can have no meaning other than to bind to the immediately surrounding "With" statement [if there is none, or the object there has no member x, then .x is meaningless]. Oracle hasn't included anything like that in Java, but such a construct would fit smoothly in the language. – supercat Feb 03 '16 at 21:15