13-技巧&工具&日誌
建造者(Builder)模式
建造者模式是日常開發中比較常見的設計模式,它的主要作用就是將複雜事物建立的過程抽象出來,該抽象的不同實現方式不同,創建出的物件也不同。
通俗的講,建立一個物件一般都會有一個固定的步驟,這個固定的步驟我們把它抽象出來,每個抽象步驟都會有不同的實現方式,不同的實現方式創建出的物件也將不同。舉個常見的例子,想必大家都買過電腦,電腦的生產或者組裝其實就是屬於建造者模式,我們知道,電腦的生產都需要安裝CPU、記憶體條、硬碟等元器件。我們可以把這個安裝步驟抽象出來,至於到底裝哪種CPU,比如i5還是i7就是對該抽象安裝步驟的具體實現。
建造者模式分為兩種,一種為經典建造者模式,另一種為變種建造者模式。
經典Builder模式
經典Buider模式中有四個角色:
- 要建造的產品Product:需要組裝的電腦
- 抽象的Builder:裝CPU、記憶體條、硬碟等抽象的步驟
- Builder的具體實現ConcreteBuilder: 對上述抽象步驟的實現,比如裝i5CPU、8G記憶體條、1T硬碟
- Director: 電腦裝機人員
接下來我們來看下用程式碼如何實現上述組裝電腦的過程
要建造的產品Product
首先我們先來建立一個Computer類
@Data @ToString public class Computer { /*CPU*/ private String CPU; /*記憶體*/ private String memory; /*硬碟*/ private String hardDisk; /*鍵盤*/ private String keyboard; /*滑鼠*/ private String mouse; }
很簡單,可以看到這個Computer類中有五個基本屬性CPU、記憶體條、硬碟、鍵盤和滑鼠,然後還有一個toString方法,用於之後方便列印資訊用。
抽象的Builder
接下來我們來建立一個抽象的電腦組裝過程的Builder類
public interface ComputerConfigBuilder {
void setCPU();
void setMemery();
void setHardDisk();
void setKeyboard();
void setMouse();
Computer getComputer();
}
電腦組裝一般都需要安裝CPU、記憶體條、硬碟、鍵盤滑鼠等,我們把這一安裝過程給抽象出來,也就是這裡的ComputerConfigBuilder ,至於具體安裝什麼需要由其實現類來實現,另外其中還定義了一個獲取Conputer的方法。
具體實現ConcreteBuilder
有了抽象的組裝過程,接下來我們就需要建立具體的實現類。
我們知道電腦一般都有低配版和高配版,不同配置,組裝成的電腦自然就不一樣。接下我們首先來建立一個低配版的套餐LowConfigBuilder ,讓其實現ComputerConfigBuilder:
public class LowConfigBuilder implements ComputerConfigBuilder {
private Computer mComputer;
public LowConfigBuilder(){
this.mComputer = new Computer();
}
@Override
public void setCPU() {
mComputer.setCPU("i5");
}
@Override
public void setMemery() {
mComputer.setMemory("8G");
}
@Override
public void setHardDisk() {
mComputer.setHardDisk("500G");
}
@Override
public void setKeyboard() {
mComputer.setKeyboard("薄膜鍵盤");
}
@Override
public void setMouse() {
mComputer.setMouse("有線滑鼠");
}
@Override
public Computer getComputer() {
return mComputer;
}
}
可以看到這個低配版的配置為:i5的CPU、8G記憶體、500G硬碟、薄膜鍵盤和有線滑鼠。
接著我們再建立一個高配版的套餐:
public class HighConfigBuider implements ComputerConfigBuilder {
private Computer mComputer;
public HighConfigBuider(){
this.mComputer = new Computer();
}
@Override
public void setCPU() {
mComputer.setCPU("i7");
}
@Override
public void setMemery() {
mComputer.setMemory("16G");
}
@Override
public void setHardDisk() {
mComputer.setHardDisk("1T");
}
@Override
public void setKeyboard() {
mComputer.setKeyboard("機械鍵盤");
}
@Override
public void setMouse() {
mComputer.setMouse("無線滑鼠");
}
@Override
public Computer getComputer() {
return mComputer;
}
}
可以看到這個高配版的配置為:i7的CPU、16G記憶體、1T硬碟、機械鍵盤和無線滑鼠。
Director
上面我們已經定義好了兩種配置方案,接下我們還需要一名裝機人員Director
public class Director {
private ComputerConfigBuilder mBuilder;
public void setBuilder(ComputerConfigBuilder builder){
this.mBuilder = builder;
}
public void createComputer(){
mBuilder.setCPU();
mBuilder.setMemery();
mBuilder.setHardDisk();
mBuilder.setKeyboard();
mBuilder.setMouse();
}
public Computer getComputer(){
return mBuilder.getComputer();
}
}
我們需要通過setBuilder來告訴他電腦需要什麼配置,然後就可以通過createComputer來一步步組裝電腦,組裝完之後就可以呼叫getComputer方法來獲取我們需要的電腦啦。
使用
//建立裝機人員
Director director = new Director();
//===============================低配電腦====================================//
//告訴裝機人員電腦配置,這裡為低配版
director.setBuilder(new LowConfigBuilder());
//裝機人員開始組裝
director.createComputer();
//從裝機人員獲取組裝好的電腦
Computer computer = director.getComputer();
//檢視電腦配置
System.out.print("電腦配置:" + computer.toString());
//===============================高配電腦====================================//
director.setBuilder(new HighConfigBuider());
director.createComputer();
Computer computer = director.getComputer();
System.out.print("電腦配置:" + computer.toString());
輸出:
電腦配置:Computer{CPU='i5', memory='8G', hardDisk='500G', keyboard='薄膜鍵盤', mouse='有線滑鼠'}
電腦配置:Computer{CPU='i7', memory='16G', hardDisk='1T', keyboard='機械鍵盤', mouse='無線滑鼠'}
好了這就是經典的Builder模式,到這裡相信你對文章開頭Builder模式的定義有了更好的理解。
不過在我們日常開發中經典Builder模式一般不常用,用的比較多的還是變種的Builder模式,接下來我們來看下變種的Builder模式。
變種Builder模式
今天Boss突然跑過來扔了一個需求給你:需要建立一個不可變的Person物件,這個Person可以擁有以下幾個屬性:名字、性別、年齡、職業、車、鞋子、衣服、錢、房子。其中名字和性別是必須有的。
聽完後,你稍微想了下,很快給出一個符合要求的Person類。
由於要創建出的Person物件是不可變的,所以你將類中的屬性都宣告為final的,然後定義了一個引數為所有屬性的構造方法,又因為name和gender為必須項,所以你為了呼叫者方便又單獨定義了一個引數為name和gender的構造方法。
這樣Person類就好了,你信心滿滿的把這個類提交給了Boss,Boss看了,還是很滿意的,不過一段時間後,Boss向你反饋了一個問題,就是如果需要傳入非必須屬性的時候,這個構造方法呼叫起來不是很方便,因為這個構造方法引數太多了,很容易傳錯。你試了下,發現確實有這個問題,看來不能把引數全都都放在構造法中,很快你想到了用set方法設定屬性,
如果要建立物件的話只用如下操作就行了:
Person person = new Person();
person.setName("張三");
person.setAge("22");
person.setGender("男");
person.setCareer("程式設計師");
...
這樣看上去比較清晰了,只要建立一個物件,想要賦什麼值set上去就可以了。
不過你細細看了下,還是發現了不少的問題的,首先用這個set方法,違背了剛開始這個物件不可變的需求,其次用這種set方法一條一條賦值,逼格不夠高,另外用這種方式很可能會得到一個不完整的Person物件,因為當你建完了Person物件,可能出於各方面的原因有些資訊忘記set了,那麼你得到的Person物件就不是你預期的物件。
這時,你有點困惑了,只好開啟谷歌一頓搜尋,沒想到還真的找到了解決辦法,就是用下面這種變種的Builder模式:
public class Person {
/*名字(必須)*/
private final String name;
/*性別(必須)*/
private final String gender;
/*年齡(非必須)*/
private final String age;
/*鞋子(非必須)*/
private final String shoes;
/*衣服(非必須)*/
private final String clothes;
/*錢(非必須)*/
private final String money;
/*房子(非必須)*/
private final String house;
/*汽車(非必須)*/
private final String car;
/*職業(非必須)*/
private final String career;
private Person(Builder builder) {
this.name = builder.name;
this.gender = builder.gender;
this.age = builder.age;
this.shoes = builder.shoes;
this.clothes = builder.clothes;
this.money = builder.money;
this.house = builder.house;
this.car = builder.car;
this.career = builder.career;
}
public static class Builder {
private final String name;
private final String gender;
private String age;
private String shoes;
private String clothes;
private String money;
private String house;
private String car;
private String career;
public Builder(String name,String gender) {
this.name = name;
this.gender = gender;
}
public Builder age(String age) {
this.age = age;
return this;
}
public Builder car(String car) {
this.car = car;
return this;
}
public Builder shoes(String shoes) {
this.shoes = shoes;
return this;
}
public Builder clothes(String clothes) {
this.clothes = clothes;
return this;
}
public Builder money(String money) {
this.money = money;
return this;
}
public Builder house(String house) {
this.house = house;
return this;
}
public Builder career(String career) {
this.career = career;
return this;
}
public Person build(){
return new Person(this);
}
}
由於這個Person物件是不可變的,所以毫無疑問我們給他的所有屬性都加了final修飾,當然如果沒有不可變的需求也是可以不加的。
然後在Person類中定義一個內部類Builder,這個Builder內部類中的屬性要和Person中的相同,並且必須有的屬性要用final修飾,防止這些屬性沒有被賦值,其他非必須的屬性不能用final,因為如果加了final,就必須對其進行初始化,這樣這些非必須的屬性又變成必須的。
然後內部類中定義了一個構造方法,傳入必須有的屬性。其他非必須的屬性都通過方法設定,每個方法都返回Builder物件自身。最後定義了一個build方法,將Builder物件傳入Person的私有構造方法,最終返回一個物件。
接下來我們來看下Person的建立:
Person person = new Person.Builder("張三","男")
.age("22")
.money("1000000")
.car("寶馬")
.build();
是不是看上去逼格瞬間提高了,非必須的屬性可以根據需要任意設定,非常靈活,而且這樣先設定屬性再建立物件,最終獲取的物件一定是你預期的完整物件,不會像用之前set的方法建立的物件可能還沒有設定完全。