GoF Builder & Simple Builder & Fluent Interface

Z-xuan Hong
16 min readDec 24, 2020

--

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 🙏

--

--

Z-xuan Hong
Z-xuan Hong

Written by Z-xuan Hong

熱愛軟體開發、物件導向、自動化測試、框架設計,擅長透過軟工幫助企業達到成本最小化、價值最大化。單元測試企業內訓、導入請私訊信箱:t107598042@ntut.org.tw。

No responses yet