Explain Codes LogoExplain Codes Logo

Is it bad practice to make a setter return "this"?

java
fluent-interface
immutable-objects
builder-pattern
Anton ShumikhinbyAnton Shumikhin·Dec 6, 2024
TLDR

Utilizing a Fluent Interface that enables setters to return this results in method chaining. This approach links method calls together, simplifying object configuration.

Here is a Practical example:

public class FluentWidget { private int size; private String color; // Setters return "this". Clearer than a cup of Java in the morning! public FluentWidget setSize(int size) { this.size = size; return this; } public FluentWidget setColor(String color) { this.color = color; return this; } } // Usage: FluentWidget widget = new FluentWidget().setSize(42).setColor("blue");

This strategy adds clarity and succinctness! However, it does diverge from conventional Java Beans void setters. Ensure you apply this pattern with discretion, maintaining codebase consistency.

Applying Fluent Interface to Immutable Objects

For Immutable Objects, the withX syntax is key for upholding Data Integrity, while still embracing the fluidity.

public class ImmutableWidget { private final int size; private final String color; public ImmutableWidget(int size, String color) { this.size = size; this.color = color; } // Consider "withX" syntax for immutable. Safety first! public ImmutableWidget withSize(int newSize) { return new ImmutableWidget(newSize, this.color); } public ImmutableWidget withColor(String newColor) { return new ImmutableWidget(this.size, newColor); } } // Usage: ImmutableWidget widget = new ImmutableWidget(32, "red").withSize(42).withColor("blue");

Introducing the Builder pattern

The Builder Pattern expands on the idea of method chaining. It simplifies complex object creation, removing the need for an over-complicated constructor.

public class WidgetBuilder { private int size; private String color; // "Make it so, number one" public WidgetBuilder setSize(int size) { this.size = size; return this; } public WidgetBuilder setColor(String color) { this.color = color; return this; } public FluentWidget build() { return new FluentWidget(size, color); } } // Usage: FluentWidget widget = new WidgetBuilder().setSize(42).setColor("blue").build();

A tip on a side: Naming setters as chainSetXyz may reveal the purpose of the chaining.

The impact on tooling and team perception

Consider the Tooling and Libraries. Depending on your environment, some might not handle non-standard setter practices.

On the human side, some developers find chaining "ghastly", while others view it as the conduit to cleaner code. Knowing your team's preference is key in maintaining the code.

Steering clear of monster constructors

Long constructors with endless parameters are error-prone and challenging to read. Using this in setters or a builder creates a much-needed shield against monster constructors.

Dispatcher's note: Managing code architecture

Watch out for a potential violation of the single responsibility principle (SRP) as each method does more than its intended role. Keep in mind the potential impacts on JVM optimizations.

The return journey: return type changes

Changing the return type of setters in the future may hinder chaining flexibility. Design with iterations in mind. Future you will appreciate it!

Alternatives: Factory or static methods

Factory or static methods offer an alternative pathway, providing clear object creation without verbose constructors or the need for complex fluent interfaces.