- 建造者模式:
- 建造者模式定義
- 建造者模式應(yīng)用場景
- 實(shí)現(xiàn)案例
- Jdk中的建造者模式
- 建造者模式的優(yōu)點(diǎn)
- 建造者模式的缺點(diǎn)
建造者模式定義
Builder模式旨在將“復(fù)雜”對象的構(gòu)造與其表示分開,以便相同的構(gòu)造過程可以創(chuàng)建不同表示。
建造者模式應(yīng)用場景
在Java編程中final類及其對象有許多好處(可以參考閱讀String的實(shí)現(xiàn)),建造者模式可以幫助我們創(chuàng)建具有大量狀態(tài)屬性的不可變類對象。
在實(shí)際編程中,我們經(jīng)常會有一些業(yè)務(wù)模塊中有些對象,一旦創(chuàng)建其中的某些狀態(tài)屬性就不可變的情景,比如網(wǎng)約車的Trip模塊(有id、owner、departure、destination、amount等屬性),很多的狀態(tài)一旦創(chuàng)建我們認(rèn)為再發(fā)生變化是沒有意義的;再比如我們在后端服務(wù)中構(gòu)建多查詢條件時,我們希望一個查詢條件一旦被構(gòu)建則不希望其發(fā)生變化。
正常情況下,如果我們通過構(gòu)建一個普通的Java類來定義Trip,我們需要為其final屬性字段定義構(gòu)造函數(shù),但是隨著系統(tǒng)的迭代,當(dāng)后期再次為Trip新增final字段時就不得不為Trip類新增一個新的參數(shù)構(gòu)造器,這是及其糟糕的,這時候如果我們一開始便采用建造者模式來定義Trip則后期可以切合“相同的構(gòu)造過程創(chuàng)建不同表示”的定義。
public Trip(String id) {...}
public Trip(String id, String owner) {...}
public Trip(String id, String owner, String departure, String destination) {...}
當(dāng)然你也可以通過setter方法來設(shè)置普通類型對象,或者創(chuàng)建更多參數(shù)的構(gòu)造函數(shù)。
實(shí)現(xiàn)案例
下面代碼示例是我自己隨機(jī)定義的,如果你的團(tuán)隊(duì)使用過Protocol Buffers協(xié)議來構(gòu)造Java類的話,那么你應(yīng)該對建造者模式非常熟悉了,Protobuf中最經(jīng)典的就是建造者模式實(shí)現(xiàn)。
package com.iblog.pattern.builder;
public class Trip {
private final String id;
private final String owner;
private final String departure;
private final String destination;
private final long amount;
private final long expiredAt;
private final String createdBy;
private final long createdAt;
private final String lastUpdatedBy;
private final long lastUpdatedAt;
private Trip(Builder builder) {
this.id = builder.id;
this.owner = builder.owner;
this.departure = builder.departure;
this.destination = builder.destination;
this.amount = builder.amount;
this.expiredAt = builder.expiredAt;
this.createdBy = builder.createdBy;
this.createdAt = builder.createdAt;
this.lastUpdatedBy = builder.lastUpdatedBy;
this.lastUpdatedAt = builder.lastUpdatedAt;
}
public Builder toBuilder() {
return new Builder()
.setId(this.id)
.setOwner(this.owner)
.setDeparture(this.departure)
.setDestination(this.destination)
.setAmount(this.amount)
.setExpiredAt(this.expiredAt)
.setCreatedBy(this.createdBy)
.setCreatedAt(this.createdAt)
.setLastUpdatedBy(this.lastUpdatedBy)
.setLastUpdatedAt(this.lastUpdatedAt);
}
public static class Builder {
private String id;
private String owner;
private String departure;
private String destination;
private long amount;
private long expiredAt;
private String createdBy;
private long createdAt;
private String lastUpdatedBy;
private long lastUpdatedAt;
/**
* In actual combat, you can define the required fields as final types,
* then set fields values through this constructor method.
*/
public Builder() {}
public Trip build() {
return new Trip(this);
}
public Builder setId(String id) {
this.id = id;
return this;
}
public Builder setOwner(String owner) {
this.owner = owner;
return this;
}
public Builder setDeparture(String departure) {
this.departure = departure;
return this;
}
public Builder setDestination(String destination) {
this.destination = destination;
return this;
}
public Builder setAmount(long amount) {
this.amount = amount;
return this;
}
public Builder setExpiredAt(long expiredAt) {
this.expiredAt = expiredAt;
return this;
}
public Builder setCreatedBy(String createdBy) {
this.createdBy = createdBy;
return this;
}
public Builder setCreatedAt(long createdAt) {
this.createdAt = createdAt;
return this;
}
public Builder setLastUpdatedBy(String lastUpdatedBy) {
this.lastUpdatedBy = lastUpdatedBy;
return this;
}
public Builder setLastUpdatedAt(long lastUpdatedAt) {
this.lastUpdatedAt = lastUpdatedAt;
return this;
}
}
public String getId() {
return id;
}
public String getOwner() {
return owner;
}
public String getDeparture() {
return departure;
}
public String getDestination() {
return destination;
}
public long getAmount() {
return amount;
}
public long getExpiredAt() {
return expiredAt;
}
public String getCreatedBy() {
return createdBy;
}
public long getCreatedAt() {
return createdAt;
}
public String getLastUpdatedBy() {
return lastUpdatedBy;
}
public long getLastUpdatedAt() {
return lastUpdatedAt;
}
@Override
public String toString() {
// you can cover this method.
return super.toString();
}
}
編寫一個使用測試類:
package com.iblog.pattern.builder;
import org.junit.Test;
import static org.junit.Assert.*;
public class TripTest {
@Test
public void testTripBuilder() {
Trip.Builder builder = new Trip.Builder();
builder.setId("1")
.setOwner("lance")
.setDeparture("wuhan")
.setDestination("shanghai");
Trip trip = builder.build();
assertEquals("1", trip.getId());
assertEquals("lance", trip.getOwner());
assertEquals("wuhan", trip.getDeparture());
assertEquals("shanghai", trip.getDestination());
assertEquals(0, trip.getAmount());
assertNull(trip.getCreatedBy());
}
}
Jdk中的建造者模式
java.lang.Appendable接口所有的實(shí)現(xiàn)類都是建造者模式的實(shí)例:
- java.lang.StringBuilder#appen()
- java.lang.StringBuffer#appen()
- java.nio.ByteBuffer#put()
建造者模式的優(yōu)點(diǎn)
從案例中使用建造者模式之后,代碼行數(shù)增加了一倍;但是建造者模式的優(yōu)勢在于,代碼的設(shè)計(jì)更加靈活,而且美觀易讀;可以以高度可讀的形式呈現(xiàn)給他人。
建造者模式可以去除多參構(gòu)造函數(shù),使得代碼更容易擴(kuò)展,也無需考慮構(gòu)造函數(shù)中傳遞null值;對于及其必要屬性可以在建造者類中通過構(gòu)造器設(shè)置,構(gòu)建不可變對象邏輯非常清晰簡明;在使用建造者模式聲明的Object實(shí)例化過程中,調(diào)用者可以很方便靈活地設(shè)置屬性,知道調(diào)用build方法。
建造者模式的缺點(diǎn)
建造者模式會使得代碼行數(shù)增加,甚至超過setter和getter方法的一倍,這是建造者模式最大的缺點(diǎn),但是這并不能掩蓋建造者模式代碼高度可讀的優(yōu)點(diǎn)。
代碼參考:pattern-example