過去寫鏈式調用感覺又臭又長,這種編碼方式的代碼看着很不爽,且不明白調用的順序。而且老師教的時候就已經習慣了,“一行一句,分號結尾”。現在呢,感覺又要極力推崇鏈式調用了,因爲它還真方便!當你慢慢熟悉之後就會發現這樣寫無論在可讀性和代碼量上都有優勢。
在講鏈式調用之前,還是先說一下,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這個牛逼的工具類如何幫助你更加簡潔的使用鏈式調用.
參考: