設計模式實戰系列之@Builder和建造者模式
阿新 • • 發佈:2020-08-12
## 前言
備受爭議的`Lombok`,有的人喜歡它讓程式碼更整潔,有的人不喜歡它,巴拉巴拉一堆原因。在我看來`Lombok`唯一的缺點可能就是需要安裝外掛了,但是對於業務開發的專案來說,它的優點遠遠超過缺點。
> 我們可以看一下,有多少專案使用了Lombok(數量還在瘋漲中...)
![](https://img.iisheng.cn/lombok-used-by.png)
> 儘管如此,我們今天也只是單純的來看一下@Builder()這個東西
## @Builder的使用
### 使用@Builder修飾類
```Java
@Data
@Builder
public class UserDO {
private Long id;
private String name;
}
```
### 使用建造者模式建立類
```Java
@Test
public void test() {
UserDO userDO = UserDO.builder()
.id(1L)
.name("iisheng")
.build();
System.out.println(userDO);
}
```
## 編譯後原始碼
執行`javac -cp ~/lombok.jar UserDO.java -verbose`將`.java`編譯成`.class`檔案。
通過IDE檢視該`.class`原始碼
> 下面展示的是被我處理後的原始碼,感興趣的同學,可以自己執行上面命令,檢視完整原始碼
```Java
public class UserDO {
private Long id;
private String name;
public String toString() {
return "UserDO(id="
+ this.getId() + ", name=" + this.getName() + ")";
}
UserDO(Long var1, String var2) {
this.id = var1;
this.name = var2;
}
public static UserDO.UserDOBuilder builder() {
return new UserDO.UserDOBuilder();
}
private UserDO() {
}
public static class UserDOBuilder {
private Long id;
private String name;
UserDOBuilder() {
}
public UserDO.UserDOBuilder id(Long var1) {
this.id = var1;
return this;
}
public UserDO.UserDOBuilder name(String var1) {
this.name = var1;
return this;
}
public UserDO build() {
return new UserDO(this.id, this.name);
}
}
}
```
由此,我們可以看出來Builder的實現步驟:
- 在`UserDO`中建立靜態`UserDOBuilder`
- 編寫設定屬性方法,返回`UserDOBuilder`物件
- 編寫`build()`方法,返回`UserDO`物件
是不是很簡單?我曾經看過不知道哪個大佬說的一句話,整潔的程式碼不是說,行數更少,字數更少,而是閱讀起來邏輯更清晰。所以,我覺得,哪怕我們不用@Builder,也應該多用這種建造者模式。
> 是時候看看什麼是建造者模式了!
## 建造者模式
### UML類圖
> 這是大部分書籍網路中的建造者模式類圖
![](https://img.iisheng.cn/builder-pattern.png)
### 產品類
```Java
public class Product {
private String name;
private Integer val;
Product(String name, Integer val) {
this.name = name;
this.val = val;
}
@Override
public String toString() {
return "Product is " + name + " value is " + val;
}
}
```
### 抽象建造者
```Java
public abstract class Builder {
protected Integer val;
protected String name;
// 設定產品不同部分,以獲得不同的產品
public abstract void setVal(Integer val);
// 設定名字 公用方法
public void setName(String name) {
this.name = name;
}
// 建造產品
public abstract Product buildProduct();
}
```
### 具體建造者
```Java
public class ConcreteBuilder extends Builder {
@Override
public void setVal(Integer val) {
/**
* 產品類內部的邏輯
* 實際儲存的值是 val + 100
*/
this.val = val + 100;
}
@Override
// 組建一個產品
public Product buildProduct() {
// 這塊還可以寫特殊的校驗邏輯
return new Product(name, val);
}
}
```
### 導演類
```Java
public class Director {
private Builder builder = new ConcreteBuilder();
public Product getAProduct() {
// 設定不同的零件,產生不同的產品
builder.setName("ProductA");
builder.setVal(2);
return builder.buildProduct();
}
}
```
> 我更喜歡這樣的建造者模式類圖
![](https://img.iisheng.cn/modified-builder.png)
`Product`的建立,也依賴於`Builder`。程式碼只需要將上面的`Product`和`ConcreteBuilder`調整一下即可。
### 調整後的產品類
```Java
public class Product {
private String name;
private Integer val;
Product(Builder builder) {
this.name = builder.name;
this.val = builder.val;
}
@Override
public String toString() {
return "Product is " + name + " value is " + val;
}
}
```
這程式碼只是將構造方法改了,使用`Builder`來建立`Product`物件。
### 調整後的具體建造者
```Java
public class ConcreteBuilder extends Builder {
@Override
public void setVal(Integer val) {
/**
* 產品類內部的邏輯
* 實際儲存的值是 val + 100
*/
this.val = val + 100;
}
@Override
// 組建一個產品
public Product buildProduct() {
// 這塊還可以寫特殊的校驗邏輯
return new Product(this);
}
}
```
相應的使用帶`Builder`的`Product`的構造方法。
## JDK中的建造者模式
> StringBuilder (擷取部分原始碼)
### 抽象建造者
```Java
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;
/**
* The count is the number of characters used.
*/
int count;
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
// Documentation in subclasses because of synchro difference
@Override
public AbstractStringBuilder append(CharSequence s) {
if (s == null)
return appendNull();
if (s instanceof String)
return this.append((String)s);
if (s instanceof AbstractStringBuilder)
return this.append((AbstractStringBuilder)s);
return this.append(s, 0, s.length());
}
public AbstractStringBuilder delete(int start, int end) {
if (start < 0)
throw new StringIndexOutOfBoundsException(start);
if (end > count)
end = count;
if (start > end)
throw new StringIndexOutOfBoundsException();
int len = end - start;
if (len > 0) {
System.arraycopy(value, start+len, value, start, count-end);
count -= len;
}
return this;
}
}
```
### 具體建造者
```java
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
@Override
public StringBuilder append(CharSequence s) {
super.append(s);
return this;
}
/**
* @throws StringIndexOutOfBoundsException {@inheritDoc}
*/
@Override
public StringBuilder delete(int start, int end) {
super.delete(start, end);
return this;
}
}
```
`StringBuilder`中的建造者模式比較簡單,但是我的確沒找到`StringBuilder`非要用建造者模式的原因,或許就是想讓我們寫下面這樣的程式碼?
```Java
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
sb.append("Love ")
.append("iisheng !")
.insert(0, "I ");
System.out.println(sb);
}
```
> 但是我希望你能通過`StringBuilder`,感受一下建造者模式的氣息
## Guava Cache中的建造者模式
### 如何使用 Guava Cache?
```Java
public static void main(String[] args) {
Load