iT邦幫忙

2021 iThome 鐵人賽

DAY 8
0
Software Development

也該是時候學學 Design Pattern 了系列 第 8

Day 08: Creational patterns - Builder

目的

將複雜物件的建造過程標準化,確保在建立細節不同的物件時,可以避免步驟的遺漏。

說明

生產飲料(物件)時,步驟是相同的,但是細節不同,假如在生產時遺漏幾個步驟,那最終的產物就不是我們所想要的。於是,可以先建立標準作業程序(SOP),告知全部生產步驟,細節則由實際生產制定。
最後建立一個指揮者,是外界唯一的聯絡窗口,外界不會知道生產的過程,只會知道最終的產品。

作法是:

  1. 成立標準作業程序(SOP),視為親代,使用 Abstract Class
  2. 建立一系列飲料產線,視為子代。
  3. 建立指揮者,負責生產,同時是外界唯一窗口。
  4. 使用者告知指揮者要生產哪款飲料。
  5. 順利取得指定的飲料。

UML 圖

Builder Pattern UML Diagram

使用 Java 實作

工廠親代:BeverageBuilder

public abstract class BeverageBuilder {
    /**
     * 宣告開始生產
     */
    public abstract void buildStart();

    /**
     * 取得瓶胚
     */
    public abstract void getPreform();

    /**
     * 加熱瓶胚
     */
    public abstract void heatPreform();

    /**
     * 瓶胚送入吹瓶機模型內吹出成型
     */
    public abstract void moldingForm();

    /**
     * 清洗瓶身
     */
    public abstract void cleanForm();

    /**
     * 吹乾空瓶
     */
    public abstract void dryForm();

    /**
     * 飲料充填
     */
    public abstract void fillBeverage();

    /**
     * 封瓶蓋
     */
    public abstract void putOnBottleCap();

    /**
     * 套上膠膜
     */
    public abstract void putOnLabel();

    /**
     * 收縮膠膜
     */
    public abstract void shrinkLabel();

    /**
     * 宣告完成生產
     */
    public abstract void buildFinish();
}

工廠親代:CokeBuilderWaterBuilderOrangeJuiceBuilder

public class CokeBuilder extends BeverageBuilder {
    @Override
    public void buildStart() {
        System.out.println("工廠即將生產可樂");
    }

    @Override
    public void getPreform() {
        System.out.println("取得瓶胚");
        System.out.println("送入輸送帶");
    }

    @Override
    public void heatPreform() {
        System.out.println("加熱軟化瓶胚");
    }

    @Override
    public void moldingForm() {
        System.out.println("送入吹瓶機");
        System.out.println("吹出成型");
    }

    @Override
    public void cleanForm() {
        System.out.println("送入無菌室");
        System.out.println("清洗瓶身內外");
        System.out.println("消毒瓶身內外");
    }

    @Override
    public void dryForm() {
        System.out.println("吹乾瓶身");
    }

    @Override
    public void fillBeverage() {
        System.out.println("飲料裝填:可樂");
    }

    @Override
    public void putOnBottleCap() {
        System.out.println("封瓶蓋:紅色瓶蓋");
        System.out.println("離開無菌室");
    }

    @Override
    public void putOnLabel() {
        System.out.println("套上瓶身膠膜:紅色膠膜");
    }

    @Override
    public void shrinkLabel() {
        System.out.println("蒸汽收縮瓶身膠膜");
    }

    @Override
    public void buildFinish() {
        System.out.println("可樂生產完成");
    }
}

public class WaterBuilder extends BeverageBuilder {
    @Override
    public void buildStart() {
        System.out.println("工廠即將生產礦泉水");
    }

    @Override
    public void getPreform() {
        System.out.println("取得瓶胚");
        System.out.println("送入輸送帶");
    }

    @Override
    public void heatPreform() {
        System.out.println("加熱軟化瓶胚");
    }

    @Override
    public void moldingForm() {
        System.out.println("送入吹瓶機");
        System.out.println("吹出成型");
    }

    @Override
    public void cleanForm() {
        System.out.println("送入無菌室");
        System.out.println("清洗瓶身內外");
        System.out.println("消毒瓶身內外");
    }

    @Override
    public void dryForm() {
        System.out.println("吹乾瓶身");
    }

    @Override
    public void fillBeverage() {
        System.out.println("飲料裝填:礦泉水");
    }

    @Override
    public void putOnBottleCap() {
        System.out.println("封瓶蓋:白色瓶蓋");
        System.out.println("離開無菌室");
    }

    @Override
    public void putOnLabel() {
        System.out.println("套上瓶身膠膜:白色膠膜");
    }

    @Override
    public void shrinkLabel() {
        System.out.println("蒸汽收縮瓶身膠膜");
    }

    @Override
    public void buildFinish() {
        System.out.println("礦泉水生產完成");
    }
}

public class OrangeJuiceBuilder extends BeverageBuilder {
    @Override
    public void buildStart() {
        System.out.println("工廠即將生產柳橙汁");
    }

    @Override
    public void getPreform() {
        System.out.println("取得瓶胚");
        System.out.println("送入輸送帶");
    }

    @Override
    public void heatPreform() {
        System.out.println("加熱軟化瓶胚");
    }

    @Override
    public void moldingForm() {
        System.out.println("送入吹瓶機");
        System.out.println("吹出成型");
    }

    @Override
    public void cleanForm() {
        System.out.println("送入無菌室");
        System.out.println("清洗瓶身內外");
        System.out.println("消毒瓶身內外");
    }

    @Override
    public void dryForm() {
        System.out.println("吹乾瓶身");
    }

    @Override
    public void fillBeverage() {
        System.out.println("飲料裝填:柳橙汁");
    }

    @Override
    public void putOnBottleCap() {
        System.out.println("封瓶蓋:橘色瓶蓋");
        System.out.println("離開無菌室");
    }

    @Override
    public void putOnLabel() {
        System.out.println("套上瓶身膠膜:橘色膠膜");
    }

    @Override
    public void shrinkLabel() {
        System.out.println("蒸汽收縮瓶身膠膜");
    }

    @Override
    public void buildFinish() {
        System.out.println("柳橙汁生產完成");
    }
}

指揮者:BeverageDirector

public class BeverageDirector {
    private BeverageBuilder beverageBuilder;

    public void setBeverageBuilder(BeverageBuilder beverageBuilder) {
        this.beverageBuilder = beverageBuilder;
    }

    public void produceBeverage() {
        beverageBuilder.buildStart();
        beverageBuilder.getPreform();
        beverageBuilder.heatPreform();
        beverageBuilder.moldingForm();
        beverageBuilder.cleanForm();
        beverageBuilder.dryForm();
        beverageBuilder.fillBeverage();
        beverageBuilder.putOnBottleCap();
        beverageBuilder.putOnLabel();
        beverageBuilder.shrinkLabel();
        beverageBuilder.buildFinish();
    }
}

進行生產

public class BeverageBuilderSample {
    public static void main(String[] args) {
        BeverageDirector beverageDirector = new BeverageDirector();

        // 生產可樂
        System.out.println("---上午的生產目標是可樂---");
        CokeBuilder cokeBuilder = new CokeBuilder();
        beverageDirector.setBeverageBuilder(cokeBuilder);
        beverageDirector.produceBeverage();
        System.out.println("---上午目標已完成---\n");

        // 生產礦泉水
        System.out.println("---下午的生產目標是礦泉水---");
        WaterBuilder watBuilder = new WaterBuilder();
        beverageDirector.setBeverageBuilder(watBuilder);
        beverageDirector.produceBeverage();
        System.out.println("---下午目標已完成---\n");

        // 生產柳橙汁
        System.out.println("---晚上的生產目標是柳橙汁---");
        OrangeJuiceBuilder orangeJuiceBuilder = new OrangeJuiceBuilder();
        beverageDirector.setBeverageBuilder(orangeJuiceBuilder);
        beverageDirector.produceBeverage();
        System.out.println("---晚上目標已完成---");
    }
}

使用 JavaScript 實作

工廠親代:BeverageBuilder

/** @abstract */
class BeverageBuilder {
  /* 宣告開始生產 */
  buildStart() { return; }
  /* 取得瓶胚 */
  getPreform() { return; }
  /* 加熱瓶胚 */
  heatPreform() { return; }
  /* 瓶胚送入吹瓶機模型內吹出成型 */
  moldingForm() { return; }
  /* 清洗瓶身 */
  cleanForm() { return; }
  /* 吹乾空瓶 */
  dryForm() { return; }
  /* 飲料充填 */
  fillBeverage() { return; }
  /* 封瓶蓋 */
  putOnBottleCap() { return; }
  /* 套上膠膜 */
  putOnLabel() { return; }
  /* 收縮膠膜 */
  shrinkLabel() { return; }
  /* 宣告完成生產 */
  buildFinish() { return; }
}

工廠親代:CokeBuilderWaterBuilderOrangeJuiceBuilder

lass CokeBuilder extends BeverageBuilder {
  /** @override */
  buildStart() {
    console.log("工廠即將生產可樂");
  }

  /** @override */
  getPreform() {
    console.log("取得瓶胚");
    console.log("送入輸送帶");
  }

  /** @override */
  heatPreform() {
    console.log("加熱軟化瓶胚");
  }

  /** @override */
  moldingForm() {
    console.log("送入吹瓶機");
    console.log("吹出成型");
  }

  /** @override */
  cleanForm() {
    console.log("送入無菌室");
    console.log("清洗瓶身內外");
    console.log("消毒瓶身內外");
  }

  /** @override */
  dryForm() {
    console.log("吹乾瓶身");
  }

  /** @override */
  fillBeverage() {
    console.log("飲料裝填:可樂");
  }

  /** @override */
  putOnBottleCap() {
    console.log("封瓶蓋:紅色瓶蓋");
    console.log("離開無菌室");
  }

  /** @override */
  putOnLabel() {
    console.log("套上瓶身膠膜:紅色膠膜");
  }

  /** @override */
  shrinkLabel() {
    console.log("蒸汽收縮瓶身膠膜");
  }

  /** @override */
  buildFinish() {
    console.log("可樂生產完成");
  }
}

class WaterBuilder extends BeverageBuilder {
  /** @override */
  buildStart() {
    console.log("工廠即將生產礦泉水");
  }

  /** @override */
  getPreform() {
    console.log("取得瓶胚");
    console.log("送入輸送帶");
  }

  /** @override */
  heatPreform() {
    console.log("加熱軟化瓶胚");
  }

  /** @override */
  moldingForm() {
    console.log("送入吹瓶機");
    console.log("吹出成型");
  }

  /** @override */
  cleanForm() {
    console.log("送入無菌室");
    console.log("清洗瓶身內外");
    console.log("消毒瓶身內外");
  }

  /** @override */
  dryForm() {
    console.log("吹乾瓶身");
  }

  /** @override */
  fillBeverage() {
    console.log("飲料裝填:礦泉水");
  }

  /** @override */
  putOnBottleCap() {
    console.log("封瓶蓋:白色瓶蓋");
    console.log("離開無菌室");
  }

  /** @override */
  putOnLabel() {
    console.log("套上瓶身膠膜:白色膠膜");
  }

  /** @override */
  shrinkLabel() {
    console.log("蒸汽收縮瓶身膠膜");
  }

  /** @override */
  buildFinish() {
    console.log("礦泉水生產完成");
  }
}

class OrangeJuiceBuilder extends BeverageBuilder {
  /** @override */
  buildStart() {
    console.log("工廠即將生產柳橙汁");
  }

  /** @override */
  getPreform() {
    console.log("取得瓶胚");
    console.log("送入輸送帶");
  }

  /** @override */
  heatPreform() {
    console.log("加熱軟化瓶胚");
  }

  /** @override */
  moldingForm() {
    console.log("送入吹瓶機");
    console.log("吹出成型");
  }

  /** @override */
  cleanForm() {
    console.log("送入無菌室");
    console.log("清洗瓶身內外");
    console.log("消毒瓶身內外");
  }

  /** @override */
  dryForm() {
    console.log("吹乾瓶身");
  }

  /** @override */
  fillBeverage() {
    console.log("飲料裝填:柳橙汁");
  }

  /** @override */
  putOnBottleCap() {
    console.log("封瓶蓋:橘色瓶蓋");
    console.log("離開無菌室");
  }

  /** @override */
  putOnLabel() {
    console.log("套上瓶身膠膜:橘色膠膜");
  }

  /** @override */
  shrinkLabel() {
    console.log("蒸汽收縮瓶身膠膜");
  }

  /** @override */
  buildFinish() {
    console.log("柳橙汁生產完成");
  }
}

指揮者:BeverageDirector

class BeverageDirector {
  constructor() {
    /** @type BeverageBuilder */
    this.beverageBuilder = null;
  }

  setBeverageBuilder(beverageBuilder) {
    this.beverageBuilder = beverageBuilder;
  }

  produceBeverage() {
    this.beverageBuilder.buildStart();
    this.beverageBuilder.getPreform();
    this.beverageBuilder.heatPreform();
    this.beverageBuilder.moldingForm();
    this.beverageBuilder.cleanForm();
    this.beverageBuilder.dryForm();
    this.beverageBuilder.fillBeverage();
    this.beverageBuilder.putOnBottleCap();
    this.beverageBuilder.putOnLabel();
    this.beverageBuilder.shrinkLabel();
    this.beverageBuilder.buildFinish();
  }
}

進行生產

const BeverageBuilderTest = () => {
  const beverageDirector = new BeverageDirector();

  // 生產可樂
  console.log("---上午的生產目標是可樂---");
  const cokeBuilder = new CokeBuilder();
  beverageDirector.setBeverageBuilder(cokeBuilder);
  beverageDirector.produceBeverage();
  console.log("---上午目標已完成---\n");

  // 生產礦泉水
  console.log("---下午的生產目標是礦泉水---");
  const watBuilder = new WaterBuilder();
  beverageDirector.setBeverageBuilder(watBuilder);
  beverageDirector.produceBeverage();
  console.log("---下午目標已完成---\n");

  // 生產柳橙汁
  console.log("---晚上的生產目標是柳橙汁---");
  const orangeJuiceBuilder = new OrangeJuiceBuilder();
  beverageDirector.setBeverageBuilder(orangeJuiceBuilder);
  beverageDirector.produceBeverage();
  console.log("---晚上目標已完成---");
};

BeverageBuilderTest();

總結

Builder 強調「允許細節不同」但物件之間的建立步驟必須相同,才能抽離共同步驟建立標準化流程。
這樣做的好處有幾點:

  1. 建立明確的步驟。
  2. 負責生產的程式碼可以重複利用。
  3. 符合 SRP 原則,抽離複雜的生產,讓生產負責生產、其他程式碼可以專責做自己的任務。

缺點很明顯,整體程式碼較多,每增加一次新產品(物件),程式碼會大量增加。

明天將介紹 Prototype 模式。


上一篇
Day 07: Creational patterns - Abstract Factory
下一篇
Day 09: Creational patterns - Prototype
系列文
也該是時候學學 Design Pattern 了31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言