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 模式。


<<:  REG档撰写—登录档脚本其实不难

>>:  Day 08 - Transduce II

Day 26 - Spring Security (三) DaoAuthenticationProvider

在上一篇Day 25 - Spring Security (二) UserDetailsServic...

大共享时代系列_028_云端串流游戏 ( Cloud Gaming )

云端串流游戏加速推坑的开始~ (∩^o^)⊃━☆゚.*・。 线上游戏跟云端串流游戏的差异? 线上游戏...

Day 13 | 元件状态:轮询 Polling

今天要介绍的功能 Polling ,用Google 翻译出来是「轮询」,不过这个词并不常见就是了,大...

[Day24]C# 鸡础观念- 物件导向(oop)~建构方法(Constructor)

老板我要一个猪排汉堡, 不要番茄,不要小黄瓜, 洋葱加量,加起司, 现实生活中,我们常常会在点餐时跟...