GoF Builder & Simple Builder & Fluent Interface
By: Z-Xuan Hong
Tags: Design Patterns, Builders

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 🙏