建造者模式

让简单的对象通过组合的方式构造成多种复杂对象。

建造者模式的实例

这里举例西式快餐,里面有非常多的套餐种类,但是各种套餐都是由不同种类的冷饮和汉堡组合而成。同时冷饮需要瓶子装,汉堡需要纸盒包住,那么我们可以先定义冷饮和汉堡类和它们所需要的瓶子和纸盒。

  1. // 纸盒
  2. class Wrapper {
  3. pack() {
  4. return "Wrapper";
  5. }
  6. }
  7. // 瓶子
  8. class Bottle {
  9. pack() {
  10. return "Bottle";
  11. }
  12. }
  13. // 汉堡需要纸盒包住
  14. class Burger {
  15. packing() {
  16. return new Wrapper();
  17. }
  18. }
  19. // 冷饮需要瓶子装
  20. class ColdDrink {
  21. packing() {
  22. return new Bottle();
  23. }
  24. }

那么我们肯定不止一种冷饮和一种汉堡,比如汉堡有蔬菜汉堡和肌肉汉堡,冷饮有可乐和百事。那么我们需要不同的类型和对应的价格。

  1. // 蔬菜汉堡
  2. class VegBurger extends Burger {
  3. price() {
  4. return 25.0;
  5. }
  6. name() {
  7. return "Veg Burger";
  8. }
  9. }
  10. // 肌肉汉堡
  11. class ChickenBurger extends Burger {
  12. price() {
  13. return 50.5;
  14. }
  15. name() {
  16. return "Chicken Burger";
  17. }
  18. }
  19. // 可乐
  20. class Coke extends ColdDrink {
  21. price() {
  22. return 30.0;
  23. }
  24. name() {
  25. return "Coke";
  26. }
  27. }
  28. // 百事
  29. class Pepsi extends ColdDrink {
  30. price() {
  31. return 35.0;
  32. }
  33. name() {
  34. return "Pepsi";
  35. }
  36. }

那一个套餐肯定是有多个不同冷饮和汉堡,那么我们需要用一个数组作为储存不同冷饮和汉堡的条目,以下套餐就很容易打造好了。

  1. class Meal {
  2. constructor () {
  3. const items = [];
  4. /**
  5. * 为什么不用Proxy而使用defineProperty
  6. * 因为Proxy虽然实现和defineProperty类似的功能
  7. * 但是在这个场景下,语意上是定义属性,而不是需要代理
  8. */
  9. Reflect.defineProperty(this, 'items', {
  10. get:()=>{
  11. if(this.__proto__ != Meal.prototype) {
  12. throw new Error('items is private!');
  13. }
  14. return items;
  15. }
  16. })
  17. }
  18. addItem(item){
  19. this[this.itemsName].push(item);
  20. }
  21. getCost(){
  22. let cost = 0.0;
  23. for (const item of this[this.itemsName]) {
  24. cost += item.price();
  25. }
  26. return cost;
  27. }
  28. showItems(){
  29. for (const item of this[this.itemsName]) {
  30. const nameStr = "Item : "+item.name();
  31. const packStr = "Packing : "+item.packing().pack();
  32. const priceStr = "Price : "+item.price();
  33. console.log(`${nameStr},${packStr},${priceStr}`);
  34. }
  35. }
  36. }

最后我们只要对外提供多个套餐就好了。这个叫它套餐建造者好了。

  1. class MealBuilder {
  2. prepareVegMeal (){
  3. const meal = new Meal();
  4. meal.addItem(new VegBurger());
  5. meal.addItem(new Coke());
  6. return meal;
  7. }
  8. prepareNonVegMeal (){
  9. const meal = new Meal();
  10. meal.addItem(new ChickenBurger());
  11. meal.addItem(new Pepsi());
  12. return meal;
  13. }
  14. }

最后我们只要用套餐建造者,给我们做出相应的套餐。

  1. const mealBuilder = new MealBuilder();
  2. const vegMeal = mealBuilder.prepareVegMeal();
  3. console.log("Veg Meal");
  4. vegMeal.showItems();
  5. console.log("Total Cost: " +vegMeal.getCost());
  6. const nonVegMeal = mealBuilder.prepareNonVegMeal();
  7. console.log("\nNon-Veg Meal");
  8. nonVegMeal.showItems();
  9. console.log("Total Cost: " +nonVegMeal.getCost());
  10. /**
  11. * output:
  12. * Veg Meal
  13. * Item : Veg Burger,Packing : Wrapper,Price : 25
  14. * Item : Coke,Packing : Bottle,Price : 30
  15. * Total Cost: 55
  16. *
  17. * Non-Veg Meal
  18. * Item : Chicken Burger,Packing : Wrapper,Price : 50.5
  19. * Item : Pepsi,Packing : Bottle,Price : 35
  20. * Total Cost: 85.5
  21. */

建造者模式的优势

这是一种创建复杂对象的最佳实践。尤其是复杂对象多变的情况下,通过基础组件来组合,在基础组件变更时,多种依赖于基础组件的复杂组件也能方便变更,而不需要更改多种不同的复杂组件。

上一页(单例模式)

下一页(原型模式)