看個故事
網際網路寒冬來襲, 小光越來越覺得碼農這個行當不太好混了. 年關將至, 思鄉之情也是倍切. 心底一橫, 要不直接回老家做點小買賣得了~
說做就做, 小光辭了工作, 回到老家武漢, 做起了賣熱乾麵的行當.
小光秉著科學開店, 合理經營的心思, 走訪老店, 探索人流, 最終把店開在了軟體園旁邊.
經過仔細他發現, 每個人點熱乾麵時, 要的配料還各不相同, 有的要蔥花, 有的不要蔥花要香菜, 有的不要辣, 有的要加酸菜......
小光心想, 這可難不倒我, 你要什麼不要什麼一次性告訴我, 不就得了. 很快, 熱乾麵程式出爐:
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 protocols) {
...
}
public List networkInterceptors() {
...
}
public Builder addNetworkInterceptor(Interceptor interceptor) {
...
}
public OkHttpClient build() {
return new OkHttpClient(this);
}
}
}複製程式碼
是不是和小光設計的HotDryNoodlesWithBuilder很像...
生活還將繼續, 小光繼續奮鬥著, 憧憬自己的人生巔峰夢~