一、建造者模式介绍
1. 解决的问题
主要解决在软件系统中,有时候面临着”一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
2. 定义
建造者模式是一种创建型设计模式,能够分步骤创建复杂对象。该模式允许使用相同的创建代码生成不同类型和形式的对象。
建造者模式应用于一些基本组件不变,而其组合经常变化时,此时可以将变与不变分离开。由建造者创建和提供实例,主管者管理建造出来的实例的依赖关系。
二、建造者模式优缺点
1. 优点
- 可以分步创建对象,暂缓创建步骤或递归运行创建步骤。
- 生成不同形式的产品时,你可以复用相同的制造代码。
- 单一职责原则:可以将复杂构造代码从产品的业务逻辑中分离出来。
2. 缺点
- 由于采用该模式需要新增多个类,因此代码整体复杂程度会有所增加。
三、建造者模式应用实例:单人年夜饭套餐
1. 实例场景
今年很多人都响应了就地过年的倡议,但异地过年不心酸,我们可以点一份单人年夜饭套餐来犒劳异地过年的自己!
单人多肉年夜饭套餐:
- 凉菜类:水煮花生
- 热菜类:宫保鸡丁、农家小炒肉、地锅鸡、土家红烧肉
- 主食类:米饭
- 饮品:崂山啤酒
单人混合肉蔬年夜饭套餐:
- 凉菜类:油炸花生米
- 热菜类:木须肉、椒盐里脊、手撕包菜、地三鲜
- 主食类:米饭
- 饮品:夺命大乌苏
2. 建造者模式实现
2.1 工程结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| builder-pattern └─ src ├─ main │ └─ java │ └─ org.design.pattern.builder │ ├─ model │ │ └─ cold │ │ │ ├─ BoiledPeanuts.java │ │ │ └─ FriedPeanuts.java │ │ └─ hot │ │ │ ├─ KungPaoChicken.java │ │ │ ├─ FarmhouseFriedPork.java │ │ │ ├─ LocalPotChicken.java │ │ │ ├─ TujiaBraisedPork.java │ │ │ ├─ MushuMeat.java │ │ │ ├─ SaltPepperTenderloin.java │ │ │ ├─ ShreddedCabbage.java │ │ │ └─ DiSanXian.java │ │ └─ staple │ │ │ └─ Rice.java │ │ └─ drink │ │ │ ├─ LaoShanBeer.java │ │ │ └─ WuSuBeer.java │ │ └─ Goods.java │ ├─ builders │ │ └─ MealBuilder.java │ └─ director │ └─ MealDirector.java └─ test └─ java └─ org.design.pattern.builder.test └─ MealDirectorTest.java
|
2.2 代码实现
2.2.1 菜品
菜品接口
所有菜都需要提供菜名以及价格。
1 2 3 4 5 6 7
|
public interface Goods { String getName(); float getPrice(); }
|
水煮花生
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
public class BoiledPeanuts implements Goods { @Override public String getName() { return "水煮花生"; }
@Override public float getPrice() { return 8; } }
|
2.2.2 年夜饭生成器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
|
@Getter @Setter public class MealBuilder {
private List<Goods> coldDishes;
private List<Goods> hotDishes;
private Goods stapleFood;
private Goods drink;
public float getCost() { float result = 0.0f; result += getSingleDishesCost(coldDishes); result += getSingleDishesCost(hotDishes); result += stapleFood.getPrice(); result += drink.getPrice(); return result; }
public void showMenu() { System.out.println("凉菜类:"); showSingleDishesName(coldDishes); System.out.println("热菜类:"); showSingleDishesName(hotDishes); System.out.println("主食:"); System.out.println(stapleFood.getName()); System.out.println("饮料:"); System.out.println(drink.getName()); }
private float getSingleDishesCost(List<Goods> goodsList) { float result = 0.0f; for (Goods goods : goodsList) { result += goods.getPrice(); } return result; }
private void showSingleDishesName(List<Goods> goodsList) { for (Goods goods : goodsList) { System.out.println(goods.getName()); } } }
|
2.2.3 年夜饭主管类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
|
public class MealDirector {
public MealBuilder constructMeatDinner() { MealBuilder mealBuilder = new MealBuilder(); List<Goods> coldDishes = new ArrayList<>(1); coldDishes.add(new BoiledPeanuts()); mealBuilder.setColdDishes(coldDishes); List<Goods> hotDishes = new ArrayList<Goods>(4); hotDishes.add(new KungPaoChicken()); hotDishes.add(new FarmhouseFriedPork()); hotDishes.add(new LocalPotChicken()); hotDishes.add(new TujiaBraisedPork()); mealBuilder.setHotDishes(hotDishes); mealBuilder.setStapleFood(new Rice()); mealBuilder.setDrink(new LaoShanBeer()); return mealBuilder; }
public MealBuilder constructMeanAndVegetablesDinner() { MealBuilder mealBuilder = new MealBuilder(); List<Goods> coldDishes = new ArrayList<>(1); coldDishes.add(new FriedPeanuts()); mealBuilder.setColdDishes(coldDishes); List<Goods> hotDishes = new ArrayList<Goods>(4); hotDishes.add(new MushuMeat()); hotDishes.add(new SaltPepperTenderloin()); hotDishes.add(new ShreddedCabbage()); hotDishes.add(new DiSanXian()); mealBuilder.setHotDishes(hotDishes); mealBuilder.setStapleFood(new Rice()); mealBuilder.setDrink(new WuSuBeer()); return mealBuilder; } }
|
2.3 测试验证
2.3.1 测试验证类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class MealDirectorTest { @Test public void testConstructMeatDinner() { MealDirector mealDirector = new MealDirector(); MealBuilder mealBuilder = mealDirector.constructMeatDinner(); mealBuilder.showMenu(); System.out.println("单人多肉年夜饭套餐花费:" + mealBuilder.getCost()); }
@Test public void testConstructMeanAndVegetablesDinner() { MealDirector mealDirector = new MealDirector(); MealBuilder mealBuilder = mealDirector.constructMeanAndVegetablesDinner(); mealBuilder.showMenu(); mealBuilder.getCost(); System.out.println("单人混合肉蔬年夜饭套餐:" + mealBuilder.getCost()); } }
|
2.3.2 测试结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 凉菜类: 水煮花生 热菜类: 宫保鸡丁 农家小炒肉 地锅鸡 土家红烧肉 主食: 米饭 饮料: 崂山啤酒 单人多肉年夜饭套餐花费:141.0
凉菜类: 油炸花生米 热菜类: 木须肉 椒盐里脊 手撕包菜 地三鲜 主食: 米饭 饮料: 夺命大乌苏 单人混合肉蔬年夜饭套餐:112.0
|
四、建造者模式结构

- 生成器 (Builder) 接口声明在所有类型生成器中通用的产品构造步骤。
- 具体生成器 (Concrete Builders) 提供构造过程的不同实现。 具体生成器也可以构造不遵循通用接口的产品。
- 产品 (Products) 是最终生成的对象。 由不同生成器构造的产品无需属于同一类层次结构或接口。
- 主管 (Director) 类定义调用构造步骤的顺序, 这样就可以创建和复用特定的产品配置。
- 客户端 (Client) 必须将某个生成器对象与主管类关联。 一般情况下, 只需通过主管类构造函数的参数进行一次性关联即可。 此后主管类就能使用生成器对象完成后续所有的构造任务。 但在客户端将生成器对象传递给主管类制造方法时还有另一种方式。 在这种情况下,在使用主管类生产产品时每次都可以使用不同的生成器。
设计模式并不难学,其本身就是多年经验提炼出的开发指导思想,关键在于多加练习,带着使用设计模式的思想去优化代码,就能构建出更合理的代码。
源码地址:https://github.com/yiyufxst/design-pattern-java
参考资料:
小博哥重学设计模式:https://github.com/fuzhengwei/itstack-demo-design
深入设计模式:https://refactoringguru.cn/design-patterns/catalog