过去写链式调用感觉又臭又长,这种编码方式的代码看着很不爽,且不明白调用的顺序。而且老师教的时候就已经习惯了,“一行一句,分号结尾”。现在呢,感觉又要极力推崇链式调用了,因为它还真方便!当你慢慢熟悉之后就会发现这样写无论在可读性和代码量上都有优势。
在讲链式调用之前,还是先说一下,java的一个设计模式吧 – Builder模式
Builder模式
Builder模式是一种一步一步创建一个复杂对象的设计模式,这种设计模式的精髓就主要有两点:其一,用户使用简单,并且可以在不需要知道内部构建细节的情况下,就可以构建出复杂的对象模型;其二,对于设计者来说,这是一个解耦的过程,这种设计模式可以将构建的过程和具体的表示分离开来。
Builder模式的使用场景
- 相同的方法,不同的执行顺序,产生不同的时间结果
- 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不同时
- 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的作用,这个时候用建造者模式非常适合
- 当初始化一个对象特别复杂,如参数多,且很多参数都具有默认值
链式调用
链式调用的变形太多了,先看比较经典的
package builder;
/**
* Builder模式,链式调用Demo<br>
*
* @author junehappylove
*
*/
public class User {
private final String firstName;
private final String lastName;
private final int age;
private final String phone;
private final String address;
private User(UserBuilder builder) {
this.firstName = builder.firstName;
this.lastName = builder.lastName;
this.age = builder.age;
this.phone = builder.phone;
this.address = builder.address;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public int getAge() {
return age;
}
public String getPhone() {
return phone;
}
public String getAddress() {
return address;
}
//Builder
public static class UserBuilder {
private final String firstName;
private final String lastName;
private int age;
private String phone;
private String address;
public UserBuilder(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public UserBuilder age(int age) {
this.age = age;
return this;
}
public UserBuilder phone(String phone) {
this.phone = phone;
return this;
}
public UserBuilder address(String address) {
this.address = address;
return this;
}
public User build() {
// 由于Builder是非线程安全的,所以如果要在Builder内部类中检查一个参数的合法性,
// 必需要在对象创建完成之后再检查
User user = new User(this);
if (user.getAge() < 0 || user.getAge() > 255) {
throw new IllegalStateException("Age out of range:" + user.getAge());// 线程安全
}
return user;
}
}
public static void main(String[] args) {
User june = new User.UserBuilder("Wang", "wei").age(18).address("qdao").build();
System.out.println(june.getAddress());
}
}
有几个重要的地方需要强调一下:
- User类的构造方法是私有的。也就是说调用者不能直接创建User对象。
- User类的属性都是不可变的。所有的属性都添加了
final
修饰符,并且在构造方法中设置了值。并且,对外只提供getters
方法。 - Builder模式使用了链式调用。可读性更佳。
- Builder的内部类构造方法中只接收必传的参数,并且该必传的参数使用了
final
修饰符。
再看一下一些变种的链式调用:
public class User {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public static Builder builder() {
return new Builder();
}
private User(Builder builder) {
this.username = builder.username;
this.password = builder.password;
}
public static class Builder {
private String username;
private String password;
public Builder setUserName(String username) {
this.username = username;
return this;
}
public Builder setPassword(String password){
this.password=password;
return this;
}
public User build() {
return new User(this);
}
@Override
public String toString() {
return "Builder{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
public static void main(String[] args) {
Builder builder = new User.Builder();
builder.setUserName("lss0555")
.setPassword("12345")
.build();
//或者
User user = new User.Builder().setUserName("lss0555").setPassword("123123").build();
//或者
User user1 = User.builder().setUserName("lss0555").setPassword("654321").build();
System.out.println("builder结果:"+builder.toString());
System.out.println("User结果:"+user.toString());
System.out.println("User2结果:"+user1.toString());
}
}
上面这个大架子基本不变,但是
- User的属性,没有用final修饰
- 提供了setters方法
- 提供了一个静态的builder方法,用于在User中获取Builder对象
应用
- 有位老哥,有这样经典的描述
本人在学习Java,直接先学习Netty框架,因为Netty框架是业界最流行的NIO框架之一,在学习的过程中,了解到Netty服务端启动需要先创建服务器启动辅助类ServerBootstrap,它提供了一系列的方法用于设置服务器端启动相关的参数。然而在创建ServerBootstrap实例时,发现ServerBootstrap只有一个无参的构造函数,事实上它需要与多个其它组件或者类交互。ServerBootstrap构造函数没有参数的原因是因为它的参数太多了,而且未来也可能会发生变化。Netty创造者为了解决这个问题,就引入了Builder模式。
- 有木有发现,讲Builder模式的链式调用,各路的大神都在使用
用户User
说事?为啥都不讲个其他的业务场景,哈哈,有部分原因是你抄我,我抄你;更重要的是User确实用的多哦,且翻一翻各路的开源框架中,你就会发现,链式调用一般会用在如下的场景中:
- 有用户的地方(哈哈,感觉好扯淡啊,没事一会找找)
- 权限验证系统,如Spring-security,Shiro
- 数据库驱动/便捷框架,如mysql-connection,druid,mybatis
- 上面都是猜的,写到这里,搜索发现无数的关于Builder模式和链式调用的文章,几乎千篇一律的拿用户说事,我就在想,需要这么low吗?这么高级的东西,真正的大神会去写User?
找一找,哪些大神在用链式调用,怎么用
- Spring-security
上面是Spring-security安全框架中的用户,它也认为用户
是一个复杂的结构,在安全领域,它认为:用户名,密码和权限是必须的,也确实是必须的
package builder;
import java.util.ArrayList;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
/**
* @author junehappylove
*
*/
public class SpringSecurityUser {
public static void main(String[] args) {
GrantedAuthority aut = new SimpleGrantedAuthority("ADMIN");
List<GrantedAuthority> auts = new ArrayList<>();
auts.add(aut);
UserDetails user1 = new User("june", "123", auts);//用户可以这样建立,其他属性默认为true
// 但是更多的情况是如下建立一个用户
User user2 = (User) User.withUsername("june").password("456").authorities("ADMIN")
.accountExpired(true)
.disabled(false)
.accountLocked(false)
.credentialsExpired(true)
.build();
System.out.println(user1.toString());
System.out.println(user2.toString());
}
}
- quartz
这个很明显,是一个单独的类,它是用来构建任务的,那么必须找到一个具体的任务类,也就是JobDetailImpl
,里面的构造行数都是不推荐使用的,要用任务类,使用下面方法
哎呀,框架中使用链式调用的地方太多了,像 安全框架shiro-core中的Subject,mybatis中就更多了,SQLBuilder,SQLDeleteBuilder,SQLSelectBuilder,SQLUpdateBuilder,UpdateBuilder等等。
最后,参考的最后一篇文章,这位老哥介绍的很全面,讲到java8之后,如何更加优雅的创建和使用链式调用,并且还介绍了lombok这个牛逼的工具类如何帮助你更加简洁的使用链式调用.