看個故事
互聯網寒冬來襲, 小光越來越覺得碼農這個行當不太好混了. 年關將至, 思鄉之情也是倍切. 心底一橫, 要不直接回老家做點小買賣得了~
說做就做, 小光辭了工作, 回到老家武漢, 做起了賣熱干面的行當.
小光秉著科學開店, 合理經營的心思, 走訪老店, 探索人流, 最終把店開在了軟件園旁邊.
經過仔細他發現, 每個人點熱干面時, 要的配料還各不相同, 有的要蔥花, 有的不要蔥花要香菜, 有的不要辣, 有的要加酸菜……
小光心想, 這可難不倒我, 你要什么不要什么一次性告訴我, 不就得了. 很快, 熱干面程序出爐:
public class HotDryNoodles {
private boolean addShallot;
private boolean addParsley;
private boolean addChili;
private boolean addSauerkraut;
public HotDryNoodles(boolean shallot, boolean parsley, boolean chili, boolean sauerkraut) {
this.addShallot = shallot;
this.addParsley = parsley;
this.addChili = chili;
this.addSauerkraut = sauerkraut;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("A bowl of hot-dry noodles has:");
if (this.addShallot) {
builder.append("蔥花.");
}
if (this.addParsley) {
builder.append("香菜.");
}
if (this.addChili) {
builder.append("辣椒.");
}
if (this.addSauerkraut) {
builder.append("酸菜.");
}
return builder.toString();
}
}
注意: 構造函數的參數是有順序的(蔥花.香菜.辣椒.酸菜).
你還別說, 大武漢的碼農們還真挺愛吃熱干面, 一會生意就上門了:
A: 老板, 熱干面一份, 加蔥, 加香菜, 放點酸菜.
B: 老板, 熱干面, 加蔥, 放點酸菜.
小光, 一聲”好嘞”, 立馬開工:
public class XiaoGuang {
public static void main(String[] args) {
// A
HotDryNoodles noodlesA = new HotDryNoodles(true, true, false, true);
System.out.println("Customer A wants: " + noodlesA);
// B
HotDryNoodles noodlesB = new HotDryNoodles(true, false, false, true);
System.out.println("Customer B wants: " + noodlesB);
}
}
很快出爐:
Customer A wants: A bowl of hot-dry noodles has:蔥花.香菜.酸菜.
Customer B wants: A bowl of hot-dry noodles has:蔥花.酸菜.
完美~
這時, 來了一哥們兒: 老板, 一碗熱干面, 加辣椒, 不放蔥.
小光吭哧吭哧忙活上了, 結果一端出來, 人哥們兒不樂意了, 怎么就放蔥了啊, 還沒有放辣椒~~
原來是小光搞錯了順序(參數順序)~ (也怪這哥們不按套路出牌啊)
沒有辦法, 重做一份咯~ (為了用戶體驗)
收攤之后, 小光是痛定思痛啊, 現在的工序的確很容易出問題啊:
目前的(參數)順序是固定的, 客戶要求各異, 根本不按套路來, 就很容易出錯.
目前的(參數)個數是固定的, 大多數客戶都選擇默認, 只額外提一兩個要求.
小光思考著怎么改進, 突然靈光一現:
這個不就是構建對象時有大量可選參數的問題嗎?
我可以使用Builder模式來解決這個問題啊. 原來編程無處不在, 小光暗思.
然后就開始動手改造工序了:
他買了張桌子, 用來作為配料臺(Builder)
把配料(蔥花.香菜.辣椒.酸菜)從他的熱干面(HotDryNoodlesWithBuilder)構造中移到配料臺放著了.
讓客戶自己選擇添加什么配料, 自己從配料臺上選擇添加.
那么, 添加配料的過程編程自取了:
如此一來, 他不僅僅釋放了自己的勞動力, 也不用費力去記客戶到底要哪些不要哪些的, 把基本的面做好, 放在調料臺, 讓用戶自己添加吧~~
改造后的熱干面工序:
public class HotDryNoodlesWithBuilder {
private boolean addShallot;
private boolean addParsley;
private boolean addChili;
private boolean addSauerkraut;
public HotDryNoodlesWithBuilder(Builder builder) {
this.addShallot = builder.addShallot;
this.addParsley = builder.addParsley;
this.addChili = builder.addChili;
this.addSauerkraut = builder.addSauerkraut;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("A bowl of hot-dry noodles has:");
if (this.addShallot) {
builder.append("蔥花.");
}
if (this.addParsley) {
builder.append("香菜.");
}
if (this.addChili) {
builder.append("辣椒.");
}
if (this.addSauerkraut) {
builder.append("酸菜.");
}
return builder.toString();
}
public static class Builder {
private boolean addShallot;
private boolean addParsley;
private boolean addChili;
private boolean addSauerkraut;
public Builder() {
}
public Builder withShallot() {
this.addShallot = true;
return this;
}
public Builder withParsley() {
this.addParsley = true;
return this;
}
public Builder withChili() {
this.addChili = true;
return this;
}
public Builder withSauerkraut() {
this.addSauerkraut = true;
return this;
}
public HotDryNoodlesWithBuilder build() {
return new HotDryNoodlesWithBuilder(this);
}
}
}
來看看用戶怎么用的:
public class XiaoGuang {
public static void main(String[] args) {
// with builder
HotDryNoodlesWithBuilder noodlesC = new HotDryNoodlesWithBuilder.Builder()
.withChili()
.withParsley()
.build();
System.out.println("Customer C wants: " + noodlesC);
HotDryNoodlesWithBuilder noodlesD = new HotDryNoodlesWithBuilder.Builder()
.withChili()
.withParsley()
.withSauerkraut()
.withShallot()
.build();
System.out.println("Customer D wants: " + noodlesD);
}
}
結果不能太滿意:
Customer C wants: A bowl of hot-dry noodles has:香菜.辣椒.
Customer D wants: A bowl of hot-dry noodles has:蔥花.香菜.辣椒.酸菜.
再也沒有用戶抱怨說小光搞錯了, 想要什么自己加, 順序也無所謂了. 小光也有更多的時間服務更多的客戶了…
看著來來往往的吃客, 小光憧憬著: 眼看就可以開分店, 連鎖店, 上市, 當董事長, 迎娶白富美, 走上人生巔峰了, 哈哈哈哈哈.
故事之后
讓我們重溫下GoF設計模式中的Builder模式:
Buidler模式, 是一種創建型的設計模式.
通常用來將一個復雜的對象的構造過程分離, 讓使用者可以根據需要選擇創建過程.
另外, 當這個復雜的對象的構造包含很多可選參數時, 那Builder模式可以說是不二之選了.
從這個故事中, 我們可以看到, 當熱干面的配料參數太多時, 我們很難去控制, 一旦弄錯了參數順序, 客戶就無法接受~
再來看下本故事中改造后的工序對應Builder模式的關系:
一般來說, Builder常常作為實際產品的靜態內部類來實現(提高內聚性).
故而Product,Director, Builder常常是在一個類文件中, 例如本例中的HotDryNoodlesWithBuilder.java.
這里為了更好的對應Builder模式的類圖關系, 將HotDryNoodlesWithBuilder畫了兩個~.
擴展閱讀
作為Java/Android開發者, 如果你想設計一個通用的庫, Builder模式幾乎肯定是會用到的, 我們需要提供良好的入口/接口來使用者可以彈性地構造他們需要的對象.
像一些優秀的開源庫, 例如Glide, OkHttp等都在構造Glide, OkHttpClient時用到Builder模式, 例如OkHttpClient的創建, 如下節選OkHttpClient.java的代碼:
public class OkHttpClient implements Cloneable, Call.Factory {
public OkHttpClient() {
this(new Builder());
}
private OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher;
this.proxy = builder.proxy;
this.protocols = builder.protocols;
this.connectionSpecs = builder.connectionSpecs;
this.interceptors = Util.immutableList(builder.interceptors);
this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
this.proxySelector = builder.proxySelector;
this.cookieJar = builder.cookieJar;
this.cache = builder.cache;
this.internalCache = builder.internalCache;
this.socketFactory = builder.socketFactory;
// more code...
}
public static final class Builder {
public Builder() {
...
}
public Builder cache(Cache cache) {
...
}
public Builder dispatcher(Dispatcher dispatcher) {
...
}
public Builder protocols(List<Protocol> protocols) {
...
}
public List<Interceptor> networkInterceptors() {
...
}
public Builder addNetworkInterceptor(Interceptor interceptor) {
...
}
public OkHttpClient build() {
return new OkHttpClient(this);
}
}
}
是不是和小光設計的HotDryNoodlesWithBuilder很像…
生活還將繼續, 小光繼續奮斗著, 憧憬自己的人生巔峰夢~