java/android 設計模式學習筆記(10)---建造者模式
這篇部落格我們來介紹一下建造者模式(Builder Pattern),建造者模式又被稱為生成器模式,是創造性模式之一,與工廠方法模式和抽象工廠模式不同,後兩者的目的是為了實現多型性,而 Builder 模式的目的則是為了將物件的構建與展示分離。Builder 模式是一步一步建立一個複雜物件的建立型模式,它允許使用者在不知道內部構建細節的情況下,可以更精細地控制物件的構造流程。一個複雜的物件有大量的組成部分,比如汽車它有車輪、方向盤、發動機、以及各種各樣的小零件,要將這些部件裝配成一輛汽車,這個裝配過程無疑會複雜,對於這種情況,為了實現在構建過程中對外部隱藏具體細節,就可以使用 Builder 模式將部件和組裝過程分離,使得構建過程和部件都可以自由擴充套件,同時也能夠將兩者之間的耦合降到最低。
轉載請註明出處:
PS:對技術感興趣的同鞋加群544645972一起交流。
設計模式總目錄
特點
將一個複雜物件的構建和它的表示分離,使得同樣的構建過程可以建立不同的表示。Builder 模式適用的使用場景:
- 相同的方法,不同的執行順序,產生不同的事件結果;
- 多個部件或零件,都可以裝配到一個物件中,但是產生的執行結果又不相同時;
- 產品類非常複雜,或者產品類中的呼叫順序不同產生不同的作用,這個時候使用建造者模式非常適合;
- 當初始化一個物件特別複雜,如引數多,且很多引數都具有預設值時。
UML類圖
Builder 模式的 uml 類圖如下所示:
有四個角色:
- Product 產品模組 產品的相關類;
- Builder 介面或抽象類 規範產品的元件,一般是由子類實現具體的元件過程,需要注意的是這個角色在實際使用過程中可以省略,最典型的就是像 AlertDialog.Builder 一樣,省略 Builder 虛擬類,將 ConcreteBuilder 寫成一個靜態內部類;
- ConcreateBuilder 類 具體的 Builder 類;
- Director 類 統一組裝過程,同樣值得注意的是,在現實開發過程中,Director 角色也經常會被省略,而直接使用一個 Builder 來進行物件的組裝,這個 Builder 通常為鏈式呼叫,也就是上面提到的
據此我們可以寫出 Builder 模式的通用程式碼:
Product.class
public class Product {
public int partB;
public int partA;
public int getPartA() {
return partA;
}
public void setPartA(int partA) {
this.partA = partA;
}
public int getPartB() {
return partB;
}
public void setPartB(int partB) {
this.partB = partB;
}
@Override
public String toString() {
return "partA : " + partA + " partB : " + partB;
}
}
產品類在此聲明瞭兩個 setter 方法,然後是 Builder 相關類:
Builder.class
public abstract class Builder {
public abstract void buildPartA(int partA);
public abstract void buildPartB(int partB);
public abstract Product build();
}
ConcreteBuilder.class
public class ConcreteBuilder extends Builder{
private Product product = new Product();
@Override
public void buildPartA(int partA) {
product.setPartA(partA);
}
@Override
public void buildPartB(int partB) {
product.setPartB(partB);
}
@Override
public Product build() {
return product;
}
}
Builder 這兩個類用來封裝對 Product 屬性的設定,最後在 build 方法中返回設定完屬性的 Product 物件,最後是 Director 角色:
Director.class
public class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
public void construct(int partA, int partB) {
builder.buildPartA(partA);
builder.buildPartB(partB);
}
}
封裝了 Builder 物件,最後是測試程式:
Builder builder = new ConcreteBuilder();
Director director = new Director(builder);
director.construct(1, 2);
Product product = builder.build();
Log.e("shawn", product.toString());
break;
執行結果
com.android.builderpattern E/shawn: partA : 1 partB : 2
程式碼一目瞭然,這裡需要提到的一點是針對不同的產品可以去構建不同的 ConcreteBuilder 類,使得一個 ConcreteBuilder 類對應一個 Product 類,這點和工廠方法模式很類似,我們後面也會介紹到他們兩者之間的區別。
示例與原始碼
Builder 模式在實際開發中出現和使用的頻率也是很高的,比如上面提到的 AlertDialog.Builder ,還比如非常有名的第三方開源框架 Universal-Image-Loader 庫中的 ImageLoaderConfig ,他們都是使用的靜態內部 Builder 類。
這裡的 demo 也使用最簡單的內部靜態 Builder 類去實現,精簡完之後只有 ConcreteBuilder 和 Product 角色,並且使用鏈式呼叫去實現上面提到的 fluent interface:
Computer.class
public class Computer {
private String CPU;
private String GPU;
private String memoryType;
private int memorySize;
private String storageType;
private int storageSize;
private String screenType;
private float screenSize;
private String OSType;
public static class Builder {
// Optional parameters - initialize with default values
private String CPU = "inter-i3";
private String GPU = "GTX-960";
private String memoryType = "ddr3 1666MHz";
private int memorySize = 8;//8GB
private String storageType = "hdd";
private int storageSize = 1024;//1TB
private String screenType = "IPS";
private float screenSize = 23.8f;
private String OSType = "Windows 10";
public Builder() {
}
public Builder setCPU(String CPU) {
this.CPU = CPU;
return this;
}
public Builder setGPU(String GPU) {
this.GPU = GPU;
return this;
}
public Builder setMemoryType(String memoryType) {
this.memoryType = memoryType;
return this;
}
public Builder setMemorySize(int memorySize) {
this.memorySize = memorySize;
return this;
}
public Builder setStorageType(String storageType) {
this.storageType = storageType;
return this;
}
public Builder setStorageSize(int storageSize) {
this.storageSize = storageSize;
return this;
}
public Builder setScreenType(String screenType) {
this.screenType = screenType;
return this;
}
public Builder setScreenSize(float screenSize) {
this.screenSize = screenSize;
return this;
}
public Builder setOSType(String OSType) {
this.OSType = OSType;
return this;
}
public Computer create() {
return new Computer(this);
}
}
private Computer(Builder builder) {
CPU = builder.CPU;
GPU = builder.GPU;
memoryType = builder.memoryType;
memorySize = builder.memorySize;
storageType = builder.storageType;
storageSize = builder.storageSize;
screenType = builder.screenType;
screenSize = builder.screenSize;
OSType = builder.OSType;
}
}
Computer 為產品類,它有一個 Builder 的靜態內部類用於設定相關屬性,測試程式碼:
Computer computer = new Computer.Builder()
.setCPU("inter-skylake-i7")
.setGPU("GTX-Titan")
.setMemoryType("ddr4-2133MHz")
.setMemorySize(16)
.setStorageType("ssd")
.setStorageSize(512)
.setScreenType("IPS")
.setScreenSize(28)
.setOSType("Ubuntu/Window10")
.create();
這裡需要提到的關鍵點是關於相關屬性的預設值問題:
- 對於必要的屬性值,無法給出其預設值的最好是通過 Builder 類的建構函式傳入,比如 AlertDialog.Builder 類的 Context,這樣也能防止使用時的疏忽;
- 對於非必要屬性來說,最好是為其生成一個預設的屬性值,這樣使用者只用設定需要更改的屬性即可;
- 每個 setter 函式都加上 return this 用來實現優美的 fluent interface 設計。
總結
Builder 模式在 Android 開發中也很常用,通常作為配置類的構建器將配置的構建和表示分離開來,同時也將配置從目標類中隔離開來,避免了過多的 setter 方法。Builder 模式比較常見的實現形式是通過呼叫鏈實現,這樣的方式也會使得程式碼更加簡潔和易懂,而且同時也可以避免了目標類被過多的介面“汙染”。
Builder 模式的優點:
- 將一個複雜物件的建立過程封裝起來,使得客戶端不必知道產品內部組成的細節;
- 允許物件通過多個步驟來建立,並且可以改變過程和選擇需要的過程;
- 產品的實現可以被替換,因為客戶端只看到一個抽象的介面;
- 建立者獨立,容易擴充套件。
- 會產生多餘的 Builder 物件以及 Director 物件,消耗記憶體;
- 與工廠模式相比,採用 Builder 模式建立物件的客戶,需要具備更多的領域知識。
Builder 模式 VS 工廠方法模式
Builder 模式和工廠方法模式都是屬於建立型模式,他們有一些共同點:這兩種設計模式的都將一個產品類物件的建立過程封裝起來,讓客戶端從具體產品類的生成中解耦,不必瞭解產品類構造的細節。但是其實他們兩種設計模式還是有很多不同點:
- Builder 模式允許物件的建立通過多個步驟來建立,而且可以改變這個過程,也可以選擇需要改變的屬性;工廠方法模式不一樣,它只有一個步驟,也就無法改變這個過程,更加無法選擇性改變屬性了;
- Builder 模式的目的是將複雜物件的構建和它的表示分離;而工廠方法模式則是定義一個建立物件的介面,由子類決定要例項化的類是哪一個,將例項化推遲到子類;
- 最明顯的當然還是程式碼的差異,Builder 模式中客戶端可以呼叫 set 方法,而工廠方法模式只能呼叫工廠類提供的相關方法。
Builder 模式 uml 類圖:
注:Director 類和 Builder 虛擬類可以被精簡。
工廠方法模式 uml 類圖:
uml 類圖的相似性還是很高的,所以通常我們會根據實際表現和用途來區別 Buidler 模式和工廠方法模式(這點和裝飾者模式與保護代理模式的區別類似,要從實際表現與使用的目的區別)。
建立型模式 Rules of thumb
有些時候建立型模式是可以重疊使用的,有一些抽象工廠模式和原型模式都可以使用的場景,這個時候使用任一設計模式都是合理的;在其他情況下,他們各自作為彼此的補充:抽象工廠模式可能會使用一些原型類來克隆並且返回產品物件。
抽象工廠模式,建造者模式和原型模式都能使用單例模式來實現他們自己;抽象工廠模式經常也是通過工廠方法模式實現的,但是他們都能夠使用原型模式來實現;
通常情況下,設計模式剛開始會使用工廠方法模式(結構清晰,更容易定製化,子類的數量爆炸),如果設計者發現需要更多的靈活性時,就會慢慢地發展為抽象工廠模式,原型模式或者建造者模式(結構更加複雜,使用靈活);
原型模式並不一定需要繼承,但是它確實需要一個初始化的操作,工廠方法模式一定需要繼承,但是不一定需要初始化操作;
使用裝飾者模式或者組合模式的情況通常也可以使用原型模式來獲得益處;
單例模式中,只要將構造方法的訪問許可權設定為 private 型,就可以實現單例。但是原型模式的 clone 方法直接無視構造方法的許可權來生成新的物件,所以,單例模式與原型模式是衝突的,在使用時要特別注意。