問題場景
有如下一個Person類,包含姓名、年齡、住址、電話字段,創(chuàng)建Person類時,要對屬性賦值,并且在對象創(chuàng)建后不允許在更改了。
public class Person{
// 姓名
private String name;
// 年齡
private Integer age;
// 家庭住址
private String address;
// 手機號碼
private Integer phone;
// setter getter constructor 省略
// ....
}
解決方案
1. constructor 構(gòu)造
- 使用全參構(gòu)造器 ,實參傳入會大量包含null,代碼丑陋不優(yōu)雅
- 創(chuàng)建只有name屬性的對象
new Person("zhang",null,null,null); - 創(chuàng)建有name和phone
new Person("zhang",null,null,10086);
- 創(chuàng)建只有name屬性的對象
- 構(gòu)造器重載
構(gòu)造器重載能有效避免實參出現(xiàn)大量null,但可想而知,類的屬性多了以后,會出現(xiàn)很多的構(gòu)造器。
2.setter注入
Person person = new Person();
person.setName("張三");
person.setAddress("地址");
person.setAge(11);
這樣有兩個問題,
- 多次出現(xiàn)person.setXXX,代碼丑陋。
- 不能保證屬性一定是在創(chuàng)建時賦值。
3.setter升級版 鏈式setter
傳統(tǒng)set方法返回void,可以稍作修改,直接返回Person
public Person setName(String name){
this.name = name;
return this;
}
public Person setAge(Integer age){
this.age = age;
return this;
}
public Person setAddress(String address){
this.address = address;
return this;
}
public Person setPhone(Integer phone){
this.phone = phone;
return this;
}
此時代碼就簡化為
Person person = new Person()
.setName("張三")
.setAddress("地址")
.setAge(11);
set鏈式調(diào)用解決了重復(fù)出現(xiàn)person.set的問題,但是不能保證對象屬性只在初始化時賦值。
4.builder 模式
根據(jù)setter鏈式調(diào)用的思路,演進出另一套解決方案
- 創(chuàng)建Person全參構(gòu)造器
- 新建一個Builder類
public static class Builder{
// 姓名
private String name;
// 年齡
private Integer age;
// 家庭住址
private String address;
// 手機號碼
private Integer phone;
public Builder name(String name){
this.name = name;
return this;
}
public Builder age(Integer age){
this.age = age;
return this;
}
public Builder address(String address){
this.address = address;
return this;
}
public Builder phone(Integer phone){
this.phone = phone;
return this;
}
public Person toBuilder(){
return new Person(name,age,address,phone);
}
}
- 在Person類提供一個靜態(tài)方法返回Builder實例
public static Builder builder(){ return new Builder(); } - 使用
此時的使用代碼如下
Person person = Person.builder()
.address("北京")
.age(11)
.name("lisi")
.toBuilder();
優(yōu)點:
簡化客戶端調(diào)用時代碼,保證屬性在實例創(chuàng)建時賦值
缺點:
要編寫B(tài)uilder類,對provider來說代碼量增加,且有大量冗余。
適用場景:
- 對象有很多屬性需要賦值
- 在對象初始化后不再需要重新賦值
builder在開源框架中的應(yīng)用:
- 在swagger配置ApiInfo時使用提供了builder模式創(chuàng)建,如下:
new ApiInfoBuilder()
// API 標題
.title("標題")
// API描述
.description("詳情請百度")
// 版本號
.version(1.0)
.license("apache 2.0")
.build();
根據(jù)名稱,很容易知道,目標類是ApiInfo,構(gòu)造器是ApiInfoBuilder
查看源碼,兩者都包含如下共同字段,符合build模式的基礎(chǔ)
private final String version;
private final String title;
private final String description;
private final String termsOfServiceUrl;
private final String license;
private final String licenseUrl;
private final Contact contact;
private List<VendorExtension> vendorExtensions
再查看build方法源碼,就是使用全參構(gòu)造器,創(chuàng)建一個目標實例。
public ApiInfo build() {
return new ApiInfo(title, description, version, termsOfServiceUrl, contact, license, licenseUrl, vendorExtensions);
}