First, let's get familiar with method chaining.
The essence of method chaining is that each called method returns the next object that needs to be operated on, allowing multiple methods to be executed in a single statement.
A common example of method chaining we encounter regularly is StringBuilder, like this:
StringBuilder stb = new StringBuilder()
.append("123")
.append("321")
.append("1234567");
Like StringBuilder, many Builder implementations also use method chaining for developer use. So why is the Builder pattern well-suited for a method chaining style?
Let's first look at what the Builder pattern is.
Builder Pattern
The Builder pattern is a design pattern that describes the object creation process. Unlike directly calling new Object(), we do not directly new the target object ourselves. Instead, we first create its builder Builder, then use the Builder to create the final object.
For example, creating a client object in OkHttp3:
OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(30, TimeUnit.SECONDS)
.addInterceptor(new CacheInterceptor(new Cache(new File("cache"), 20000)))
.build();
You might ask: why not just directly new OkHttpClient(), instead of going through an extra object to create the object we need?
First, each object's builder may serve a different purpose, and common reasons for using a builder include:
- Too many parameters: using
setmethods results in overly verbose code that is not concise setoperations require internal logic: a builder proxy approach is used to keep thesetinterface of the target class clean- Shielding parameter constraints of the target class: for example, too many
finalparameters make direct construction of the target class inconvenient - Enables lazy construction to reduce unnecessary performance overhead
Therefore, the Builder pattern is essentially a tool to help us construct objects, and method chaining makes the construction process very intuitive, easier to write and understand.
Example
For example, a builder can be implemented like this:
public class User {private String name; private String id; /* Getter&Setter */ public static final class Builder { private final User user; public Builder() { this.user = new User(); } public Builder name(String name) { user.setName(name); return this; } public Builder id(String id) { user.setId(id); return this; } public User build() { return user; } }
}
Here we use an inner static class to proxy each parameter, so we can generate a User object in the following way:
User user = new User.Builder().id("123").name("小明").build();
At this point you may notice another question: why write such a long chain when we could just use the more concise constructor form new User("123", "小明")?
Because normally, we do not use the Builder pattern when there are few parameters. Builders are generally only used when object construction is complex.
Summary
The Builder pattern only exists to simplify object creation. In fact, for simple POJO objects, we can even directly implement builder-style set methods:
public class User {private String name; private String id; public String getName() { return name; } public User setName(String name) { this.name = name; return this; } public String getId() { return id; } public User setId(String id) { this.id = id; return this; }
}
Design patterns are just programming experience validated by time and large volumes of code, but they are not absolute truth. As the industry develops, design patterns will continue to evolve and change; only what fits you (or your requirements) is the best choice.
This discussion topic was separated from the original thread at https://juejin.cn/post/7369050990618476571