—-慢慢来比较快,虚心学技术—-
**

概念

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示

即将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。

该模式有下列几个角色:

Builder (**抽象建造者)为创建一个产品对象的各个部件指定抽象接口**。

ConcreteBuilder (**具体建造者)实现Builder的接口**以构造和装配该产品的各个部件,定义并明确它所创建的表示,并提供一个检索产品的接口。

Director (指挥者):构造一个使用Builder接口的对象。调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。

Product (**产品)被构造的复杂对象**。

推演

需求**:KFC购买食物套餐,主要包括主食(StapleFood)、小吃(Snack)、饮料(Drink)三样**

反例(传统套路)

创建购买对象Meal

  1. @Getter
  2. @Setter
  3. @ToString
  4. class Meal{
  5. /**
  6. * 主食
  7. */
  8. private String stapleFood;
  9. /**
  10. * 小吃
  11. */
  12. private String snack;
  13. /**
  14. * 饮料
  15. */
  16. private String drink;
  17. }

小明和小李购买组合如下:

  1. Meal meal = new Meal();
  2. meal.setStapleFood("无敌汉堡包");
  3. meal.setSnack("炸薯条");
  4. meal.setDrink("阔落");
  5. System.out.println(meal);

结果:

  1. Meal(stapleFood=无敌汉堡包, snack=炸薯条, drink=阔落)

问题:也就是说,我是顾客,去购买一个套餐还要自己负责食物组合,食物装填这些细节工作,实在是太麻烦,我知道的太多了(违反了迪米特法则

正例1

针对上一问题,专门创建一个“MealBuilder” 建造者类,这个类负责封装装填套餐的过程(即创建套餐的过程)

  1. class MealBuilder{
  2. private Meal meal = new Meal();
  3. public Meal build(){
  4. meal.setStapleFood("无敌汉堡包");
  5. meal.setSnack("炸薯条");
  6. meal.setDrink("阔落");
  7. return meal;
  8. }
  9. }

此时再去购买套餐便是如下:

  1. Meal meal = new MealBuilder().build();
  2. System.out.println(meal);

结果:

  1. Meal(stapleFood=无敌汉堡包, snack=炸薯条, drink=阔落)

这种写法的优点是:客户端无需知道目标产品的细节,只需要调用建造者即可,建造者封装了创建产品的复杂过程

问题**:封装太死,只有一种套餐,无法满足变化需求

正例2

针对上述问题,我们可以创建多个不同的建造者,用于生产不同细节的产品,如下:

  1. /**
  2. * 无敌套餐-建造者
  3. */
  4. class InvincibleMealBuilder{
  5. private Meal meal = new Meal();
  6. public Meal build(){
  7. meal.setStapleFood("无敌汉堡包");
  8. meal.setSnack("炸薯条");
  9. meal.setDrink("阔落");
  10. return meal;
  11. }
  12. }
  13. /**
  14. * 巨无霸套餐-建造者
  15. */
  16. class BigMacMealBuilder{
  17. private Meal meal = new Meal();
  18. public Meal build(){
  19. meal.setStapleFood("巨无霸汉堡包");
  20. meal.setSnack("香芋丸子");
  21. meal.setDrink("咖啡");
  22. return meal;
  23. }
  24. }

此时再去购买套餐便是如下:

  1. //给小胖来个巨无霸套餐
  2. Meal bmm = new BigMacMealBuilder().build();
  3. System.out.println(bmm);
  4. //给小明来个无敌套餐
  5. Meal im = new InvincibleMealBuilder().build();
  6. System.out.println(im);

结果:

  1. Meal(stapleFood=巨无霸汉堡包, snack=香芋丸子, drink=咖啡)
  2. Meal(stapleFood=无敌汉堡包, snack=炸薯条, drink=阔落)

优点:满足了不同套餐的需求

缺点**:1、建造者频繁出现重复代码
2、建造过程不稳定,如果某个建造者没有执行某个过程(如未装填薯条/阔落),得到的产品将会出现问题。**

正例3

为了解决上述问题,我们将为建造者创造一份规范(抽象),建造者按照规定流程执行,使建造者的建造过程稳定下来

  1. /**
  2. * 建造者抽象
  3. */
  4. interface Builder{
  5. void setStapleFood();
  6. void setSnack();
  7. void setDrink();
  8. Meal build();
  9. }

建造者们直接实现上述接口:

  1. /**
  2. * 无敌套餐-建造者
  3. */
  4. class InvincibleMealBuilder implements Builder{
  5. private Meal meal = new Meal();
  6. @Override
  7. public void setStapleFood() {
  8. meal.setStapleFood("无敌汉堡包");
  9. }
  10. @Override
  11. public void setSnack() {
  12. meal.setSnack("炸薯条");
  13. }
  14. @Override
  15. public void setDrink() {
  16. meal.setDrink("阔落");
  17. }
  18. @Override
  19. public Meal build(){
  20. return meal;
  21. }
  22. }
  23. /**
  24. * 巨无霸套餐-建造者
  25. */
  26. class BigMacMealBuilder implements Builder{
  27. private Meal meal = new Meal();
  28. @Override
  29. public void setStapleFood() {
  30. meal.setStapleFood("巨无霸汉堡包");
  31. }
  32. @Override
  33. public void setSnack() {
  34. meal.setSnack("香芋丸子");
  35. }
  36. @Override
  37. public void setDrink() {
  38. meal.setDrink("咖啡");
  39. }
  40. @Override
  41. public Meal build(){
  42. return meal;
  43. }
  44. }

此时再去购买套餐便是如下:

  1. //给小胖来个巨无霸套餐
  2. Builder bmmBuilder = new BigMacMealBuilder();
  3. bmmBuilder.setStapleFood();
  4. bmmBuilder.setSnack();
  5. bmmBuilder.setDrink();
  6. Meal bmm =bmmBuilder.build();
  7. System.out.println(bmm);
  8. //给小明来个无敌套餐
  9. Builder imBuilder = new InvincibleMealBuilder();
  10. imBuilder.setStapleFood();
  11. imBuilder.setSnack();
  12. imBuilder.setDrink();
  13. Meal im = imBuilder.build();
  14. System.out.println(im);

优点:建造者的建造过程是稳定的,不会漏掉中间的某一步。

缺点:现在又变成了客户端组合套餐,违反了迪米特法则,现在相当于客户端指挥建造者的建造步骤去创建产品

正例4(真正的建造者模式)

创建一个指挥者角色,用于指挥建造者,即将上述指挥建造者的代码进行封装

  1. /**
  2. * 指挥者
  3. */
  4. class MealDirect{
  5. public Meal build(Builder builder){
  6. builder.setStapleFood();
  7. builder.setSnack();
  8. builder.setDrink();
  9. return builder.build();
  10. }
  11. }

此时再去购买套餐便是如下:

  1. //给小胖来个巨无霸套餐
  2. MealDirect direct = new MealDirect();
  3. Builder bmmBuilder = new BigMacMealBuilder();
  4. Meal bmm =direct.build(bmmBuilder);
  5. System.out.println(bmm);
  6. //给小明来个无敌套餐
  7. Builder imBuilder = new InvincibleMealBuilder();
  8. Meal im = direct.build(imBuilder);
  9. System.out.println(im);
  1. ** 如此便是真正的建造者模式,此时如果需要扩展一个建造者,仅仅需要创建一个建造者并将其传入direct.build()方法即可,无需修改源码,符合开闭原则**

思维推演

|——传统套路:客户端完全知道细节,自行组装套餐,违反迪米特法则
|
|——新增建造者,封装组装过程,客户端仅需要调用建造者提供的接口即可,但是封装过死,需求无法变动
|
|——创建不同的建造者,满足变化需求,但是建造者的建造过程不稳定,既建造步骤不规范,可能存在缺失
|
|——创建一个建造者抽象,规范化建造者建造过程,使建造过程稳定下来,但是此时完全由客户端指挥建造者建造产品的步骤,又违反了迪米特法则
|
|——创建一个指挥者对象,封装建造者的建造步骤调用,客户端仅需要调用指挥者去指挥建造者即可,建造者的建造细节对客户端完全封闭,符合迪米特法则和开闭原则

应用场景

创造的产品较为复杂,由多个成员组成,且成员面临着剧烈的变化,但成员间的建造顺序是稳定的

创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。

优点

简化代码及封闭构建对象的细节,让客户端的调用更简洁明了,提高代码可读性

缺点

**
同工厂模式一样,一旦产生多种产品,将会产生多个建造者,增加代码量

建造者模式与工厂模式的区别

工厂模式关注的角度是产品的创建
而建造者模式关注的角度是产品创建后的属性赋值

如有贻误,还请评论指正**