GoF Builder & Simple Builder & Fluent Interface
Builder這個詞相信大家或多或少都有聽過,但除了GoF Builder外,還有其他的Builder,不同的Builder應用場景皆不相同,此篇文章將會探討各種Builder並讓大家了解:
- Simple Builder和其使用情境
- Design Pattern Builder (GoF Builder)和其使用情境
- Fluent Interface和其使用情境
- Simple Builder、GoF Builder、Fluent Interface之間的關係
Simple Builder (Effective Java Third Edition)
What is Simple Builder?針對物件的屬性定義一系列的Setter API,並讓客戶端使用Setter API來設定物件的屬性。
Why?當物件只能在Runtime被創建時,如果沒有Simple Builder的幫助,被創立的物件需要Publish Setter API給客戶端,但此API只有在Runtime建立此物件時才需要,當物件被創建後就不需要了,多餘的Setter API會讓客戶端有機會讓破壞物件的狀態完整性 (Data Integrity),物件應該要提供Observable Behavior而不是Setter API。
What is advantage:
- 提升物件的封裝性,避免Publish Setter API
- 提供Explicit API讓客戶端更直覺的創建物件
Application Context:
- 物件的創建在於Runtime,而我們又不想讓此物件Publish Setter API
- 物件的建構子參數列過長,客戶端不好產生此物件
以下為範例程式碼
public class Pizza {
private final String name;
private final int inch;
private final int calories;
private final String crust;
private final int price;
Pizza(String name, int inch, int calories, String crust, int price) {
this.name = name;
this.inch = inch;
this.calories = calories;
this.crust = crust;
this.price = price;
}
}
從上述程式碼我們可以看到此類別的幾個重點:
- Pizza類別為Immutable
- Pizza建構子參數過長
- 這裡假設Pizza是在Runtime被產生
如果不使用Simple Builder,上述程式碼會改成:
public class Pizza {
private String name;
private int inch;
private int calories;
private String crust;
private int price;
Pizza() {
}
public void setCalories(int calories) {
this.calories = calories;
}
public void setInch(int inch) {
this.inch = inch;
}
public void setCrust(String crust) {
this.crust = crust;
}
public void setName(String name) {
this.name = name;
}
public void setPrice(int price) {
this.price = price;
}
}
上述程式碼會導致以下問題:
- Pizza類別從Immutable轉變為Mutable,客戶端能輕易的破壞物件的狀態完整性
- Pizza類別只在產生時才需要Setter API,當創建完成後就不需要,但Publish Setter API,容易造成客戶端困惑
我們可以使用Simple Builder來解決上述問題,程式碼如下:
public class Pizza {
private final String name;
private final int inch;
private final int calories;
private final String crust;
private final int price;
Pizza(String name, int inch, int calories, String crust, int price) {
this.name = name;
this.inch = inch;
this.calories = calories;
this.crust = crust;
this.price = price;
}
}public class PizzaBuilder {
private String name;
private int inch;
private int calories;
private String crust;
private int price;
public void setCalories(int calories) {
this.calories = calories;
}
public void setInch(int inch) {
this.inch = inch;
}
public void setCrust(String crust) {
this.crust = crust;
}
public void setName(String name) {
this.name = name;
}
public void setPrice(int price) {
this.price = price;
}
public Pizza build() {
return new Pizza(this.name, this.inch, this.calories, this.crust, this.price);
}
}
上述使用Simple Builder的程式碼解決了以下問題:
- Pizza類別維持Immutable的特性
- Pizza類別的Public API不會造成客戶端困惑
- PizzaBuilder提供Explicit API讓客戶產生Pizza類別
GoF Builder
What is GoF Builder?封裝Complex物件的內部結構,並且定義一系列的建構過程,讓建立複雜物件的過程 (Construction Process)與物件內部結構解耦合 (Complex Object Representation)。
Why?當我們把建立複雜物件的過程與複雜物件內部結構解耦合時,我們能夠達到Same Construction Create Different Representations的效果,也能夠讓複雜的物件專注於本身的責任,不需要處理物件組裝的責任。
What is advantage:
- 降低複雜物件的責任
- 降低客戶端對於複雜物件內部的耦合 (因為已經封裝在Builder內)
- 為了產生不同Representation的複雜物件,只需要創建Concrete Builder即可,遵守Open Closed Principle
Application Context:
- 創建的物件非常複雜,僅用Factory無法表達多種變化
- 想讓建立複雜物件的演算法與物件建立的過程、物件的內部結構解耦合
以下為範例程式碼:
public class People {
private final Hands hands;
private final Eyes eyes;
private final Nose nose;
private final Brain brain;
public People(Hands hands, Eyes eyes, Nose nose, Brain brain) {
this.hands = hands;
this.eyes = eyes;
this.nose = nose;
this.brain = brain;
}
public void doSomething() {
this.hands.move();
this.eyes.watch();
this.nose.smell();
this.brain.thinking();
}
}public interface Hands {
void move();
}public class NormalHands implements Hands {
@Override
public void move() {
// normal move ...
}
}public interface Eyes {
void watch();
}public class NormalEyes implements Eyes {
@Override
public void watch() {
// normal watch ...
}
}public interface Nose {
void smell();
}
public class NormalNose implements Nose {
@Override
public void smell() {
// normal smell
}
}public interface Brain {
void thinking();
}public class NormalBrain implements Brain {
@Override
public void thinking() {
// normal thinking ...
}
}public class SmartBrain implements Brain {
@Override
public void thinking() {
// smart thinking ...
}
}public class Main {
public static void main(String[] args) {
People normalPeople = new People(
new NormalHands(),
new NormalEyes(),
new NormalNose(),
new NormalBrain()
);
People smartPeople = new People(
new NormalHands(),
new NormalEyes(),
new NormalNose(),
new SmartBrain()
);
}
}
從上述程式碼我們可以看到此類別的幾個重點:
- People類別是一個複雜物件,經由組裝多個物件而成
- 如果讓客戶端組合這些物件,會導致客戶端耦合到Person類別的內部結構,也就是GoF俗稱的Complex Object Representation
- 所有客戶端皆會有創建複雜物件的程式碼,違反DRY,複雜物件內部結構的資訊散落在各個客戶端
我們可以使用GoF Builder來解決上述問題,程式碼如下:
public interface PeopleBuilder {
void buildHands();
void buildEyes();
void buildNose();
void buildBrian();
}public class NormalPeopleBuilder implements PeopleBuilder {
private NormalHands normalHands;
private NormalEyes normalEyes;
private NormalNose normalNose;
private NormalBrain normalBrain;
public People getPeople() {
return new People(normalHands, normalEyes, normalNose, normalBrain);
}
@Override
public void buildHands() {
this.normalHands = new NormalHands();
}
@Override
public void buildEyes() {
this.normalEyes = new NormalEyes();
}
@Override
public void buildNose() {
this.normalNose = new NormalNose();
}
@Override
public void buildBrian() {
this.normalBrain = new NormalBrain();
}
}public class SmartPeopleBuilder implements PeopleBuilder {
private NormalHands normalHands;
private NormalNose normalNose;
private NormalEyes normalEyes;
private SmartBrain smartBrain;
@Override
public void buildHands() {
normalHands = new NormalHands();
}
@Override
public void buildEyes() {
normalEyes = new NormalEyes();
}
@Override
public void buildNose() {
normalNose = new NormalNose();
}
@Override
public void buildBrian() {
smartBrain = new SmartBrain();
}
public People getPeople() {
return new People(normalHands, normalEyes, normalNose, smartBrain);
}
}public class Main {
public static void main(String[] args) {
NormalPeopleBuilder nb = new NormalPeopleBuilder();
nb.buildHands();
nb.buildEyes();
nb.buildNose();
nb.buildBrian();
People normalPeople = nb.getPeople();
SmartPeopleBuilder sb = new SmartPeopleBuilder();
sb.buildHands();
sb.buildEyes();
sb.buildNose();
sb.buildBrian();
People smartPeople = sb.getPeople();
}
}
上述使用GoF Builder的程式碼解決了以下問題:
- 提供客戶端Explicit API來創建複雜物件
- Concrete Builder封裝複雜物件的內部結構,遵守DRY,原本散落在各個客戶端的資訊被封裝在Builder
- 客戶端與複雜物件的內部結構解除耦合,修改複雜物件只會影響到Builder,遵守Local Consequence
- 同樣的建構過程會根據Concrete Builder的不同,產生不同複雜物件,上述例子為產生NormalPeople和SmartPeople
Fluent Interface
What is Fluent Interface? 提供一系列的API,客戶端能夠以Chaining的形式呼叫這些API。
Why? 提供流暢的API給客戶端使用。
What is advantage:
- 客戶端使用起來流暢
- 可讀性高
Application Context:
- 使用Simple Builder時可以搭配一起服用
- 使用GoF Builder時可以搭配一起服用
以下為範例程式碼:
public class PizzaBuilder {
private String name;
private int inch;
private int calories;
private String crust;
private int price;
public PizzaBuilder setCalories(int calories) {
this.calories = calories;
return this;
}
public PizzaBuilder setInch(int inch) {
this.inch = inch;
return this;
}
public PizzaBuilder setCrust(String crust) {
this.crust = crust;
return this;
}
public PizzaBuilder setName(String name) {
this.name = name;
return this;
}
public PizzaBuilder setPrice(int price) {
this.price = price;
return this;
}
public Pizza build() {
return new Pizza(this.name, this.inch, this.calories, this.crust, this.price);
}
}public class Main {
public static void main(String[] args) {
PizzaBuilder builder = new PizzaBuilder();
Pizza pizza = builder
.setCalories(100)
.setCrust("thin")
.setInch(15)
.setName("Good Pizza")
.setPrice(199)
.build();
}
}
Simple Builder vs GoF Builder vs Fluent Interface
Simple Builder vs GoF Builder
- 共通點為提供Explicit API給客戶端,降低客戶端創建物件的難度
- 但是兩種Builder想要解決的問題是不一樣的,Simple Builder是為了維持物件的Immutability,GoF Builder是為了封裝複雜物件內部結構與其組裝過程,達到Same Construction Create Different Representations的效果
Builder (Simple, GoF) vs Fluent Interface
- 使用Builder不一定要使用Fluent Interface,但Fluent Interface能夠讓Builder的API看起來更流暢,行雲流水。
結論:Simple Builder用來維持物件的Immutability、GoF Builder用來封裝物件內部結構與組裝過程以達到Same Construction Create Different Representations、Fluent Interface用來提升API的使用體驗
在此感謝與我討論有關Builder實作細節的各位朋友,讓我更加釐清不同Context底下應該使用哪些Builder 🙏