一、介绍

1、建造者模式

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

2、使用场景

一个设计模式解决一类问题,那么建造者模式解决了什么问题呢?——对象的构建过于复杂的问题

  • 当一个类的构造函数参数过多(超过四个),并且有的参数可有可无,或者很多产品有默认值。
  • 产品类非常复杂或者产品类因为调用顺序不同而产生不同作用

    3、优点

  • 复杂产品的创建步骤分解在不同的方法中,这些方法可以调用顺序不同,结果不同,创建结果很清晰

    4、缺点

  • 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者来实现这种变化。

二、案例

理论总是难以理解的,现在通过案例分析问题,一步步了解使用建造者模式的好处
【案例】好好看一下这个案例
KFC套餐
假如目前KFC里面有很多个套餐
> 在套餐里面有必点,也有选点,然后每个单品又有大小之分
> 必点:汉堡(hamburger),薯条(chips)
> 选点:鸡腿(chicken),可乐(cola),披萨(pizza)
【用Java代码模拟场景】
我们如何构成这么多套餐实例呢?
我们不使用建造者模式也能构建代码,但是建造者模式会让代码看上去更装逼,代码到后期更结构化更容易维护和拓展
首先构建这个实体类`KFC

  1. public class KFC {
  2. //套餐必点
  3. private String hamburger;
  4. private String chips;
  5. //套餐选点
  6. private String chicken;
  7. private String cola;
  8. private String pizza;
  9. }

想象是不是折叠构造函数来创建实例,下面来尝试一下:

  1. public class KFC{
  2. //省略了上面的属性.....
  3. //必点套餐A
  4. public KFC(String hamburger,String chips){
  5. this(hamburger,chips,null,null,null);
  6. }
  7. A
  8. //套餐B
  9. public KFC(String hamburger,String chips,String chicken){
  10. this(hamburger,chips,chicken,null,null);
  11. }
  12. //套餐C
  13. public KFC(String hamburger,String chips,String chicken,String cola){
  14. this(hamburger,chips,chicken,cola,null);
  15. }
  16. //......还有好多种组合方式,你会发现使用折叠构造函数的方法十分复杂
  17. //全选
  18. public KFC(String hamburger,String chips,String chicken,String cola,String pizza){
  19. this.hamburger = hamburger;
  20. this.chips = chips;
  21. this.chicken = chicken;
  22. this.cola = cola;
  23. this.pizza = pizza;
  24. }
  25. }

会发现使用折叠构造函数的方式很复杂,很冗余。
那么是不是可以使用set方法来创建,只要一个必点构造就好了,那继续模拟:

  1. public class KFC{
  2. //.....省略了属性
  3. //必点
  4. public KFC(String hamburger,String chips){
  5. this.hamburger = hamburger;
  6. this.chips = chips;
  7. }
  8. //set方法
  9. public void setChicken(String chicken) {
  10. this.chicken = chicken;
  11. }
  12. public void setCola(String cola) {
  13. this.cola = cola;
  14. }
  15. public void setPizza(String pizza) {
  16. this.pizza = pizza;
  17. }
  18. //实例化对象,你会发现这种方式就友好很多
  19. public static void main(String[] args) {
  20. KFC kfc = new KFC("大汉堡","大薯条");
  21. //加小份可乐
  22. kfc.setCola("小可乐");
  23. //加个鸡腿
  24. kfc.setChicken("大鸡腿");
  25. System.out.println(kfc);
  26. }
  27. }

会发现使用set方式就友好了很多,但是这种方法太随意,可能这个套餐里面没有这个单品,而使用set的人却不知道,造成错误的套餐出现。
解决上面的两种问题:一种设计模式解决一类问题,所以建造者模式就出现了。

三、建造者模式

先了解一下又哪些角色吧,看不懂没关系,看一下代码就懂了
image.png
四个角色
Product(产品角色): 一个具体的产品对象。
Builder(抽象建造者): 创建一个Product对象的各个部件指定的抽象接口。
ConcreteBuilder(具体建造者): 实现抽象接口,构建和装配各个部件。
Director(指挥者): 构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程。

拿其中两个套餐举例
> 套餐A:汉堡,薯条,大鸡腿
> 套餐B:汉堡,薯条,小鸡腿,小可乐,小披萨
> 其中薯条和汉堡可大可小,并且必须有,
> 其它的都为固定大小,但是你可以选择有或没有

  • 产品(KFC)

    1. public class KFC {
    2. //套餐必点
    3. private String hamburger;
    4. private String chips;
    5. //套餐选点
    6. private String chicken;
    7. private String cola;
    8. private String pizza;
    9. //必点
    10. public KFC(String hamburger,String chips){
    11. this.hamburger = hamburger;
    12. this.chips = chips;
    13. }
    14. //set方法
    15. public void setChicken(String chicken) {
    16. this.chicken = chicken;
    17. }
    18. public void setCola(String cola) {
    19. this.cola = cola;
    20. }
    21. public void setPizza(String pizza) {
    22. this.pizza = pizza;
    23. }
    24. }
  • Builder

    1. public interface Builder {
    2. void setChicken();
    3. void setCola();
    4. void setPizza();
    5. KFC getKFC();
    6. }
  • ConcreteBuilder

套餐A

  1. public class ConcreteBuilder1 implements Builder {
  2. private KFC kfc;
  3. //这一步非常重要
  4. public ConcreteBuilder1(String hamburger,String chips){
  5. kfc = new KFC(hamburger,chips);
  6. }
  7. @Override
  8. public void setChicken() {
  9. kfc.setChicken("大鸡腿");
  10. }
  11. @Override
  12. public void setCola() {
  13. kfc.setCola(null);
  14. System.out.println("套餐A里面没有可乐");
  15. }
  16. @Override
  17. public void setPizza() {
  18. kfc.setPizza(null);
  19. System.out.println("套餐A里面没有披萨");
  20. }
  21. @Override
  22. public KFC getKFC() {
  23. return kfc;
  24. }
  25. }

套餐B

  1. public class ConcreteBuilder2 implements Builder {
  2. private KFC kfc;
  3. //这一步非常重要
  4. public ConcreteBuilder2(String hamburger,String chips){
  5. kfc = new KFC(hamburger,chips);
  6. }
  7. @Override
  8. public void setChicken() {
  9. kfc.setChicken("小鸡腿");
  10. }
  11. @Override
  12. public void setCola() {
  13. kfc.setCola("小可乐");
  14. }
  15. @Override
  16. public void setPizza() {
  17. kfc.setPizza("小披萨");
  18. }
  19. @Override
  20. public KFC getKFC() {
  21. return kfc;
  22. }
  23. }

Director:
执行者,这里把他当作服务员,此时你像服务员点餐

  1. public class Director {
  2. public KFC build(Builder builder){
  3. //套餐里面我只选了鸡腿和可乐
  4. builder.setChicken();
  5. builder.setCola();
  6. return builder.getKFC();
  7. }
  8. }
  9. //测试
  10. public class BuilderTest {
  11. public static void main(String[] args) {
  12. //套餐A
  13. System.out.println("======套餐A======");
  14. Builder concreteBuilder1 = new ConcreteBuilder1("大汉堡", "小薯条");
  15. KFC kfc1 = new Director().build(concreteBuilder1);
  16. System.out.println(kfc1);
  17. //套餐B
  18. System.out.println("======套餐B======");
  19. Builder concreteBuilder2 = new ConcreteBuilder2("小汉堡", "小薯条");
  20. KFC kfc2 = new Director().build(concreteBuilder2);
  21. System.out.println(kfc2);
  22. }
  23. }

会觉得会有点麻烦,单品可有可无的选择上面十分的被动,代码看上去也很怪,如果下次想全部单品先选上,再去选套餐的时候,又要新建一个新的指导者。
所以普通的建造者模式不适合参数的可有可无的选择,普通的建造者模式更侧重调控次序,在有些情况下需要简化系统结构:

四、简化版的链式建造者模式

既Director是变化的,并且其实在生活中自身就是Director,所以这个时候可以把Director这个角色去掉,因为自身就是指导者。

  • 产品(product)

    1. public class KFC {
    2. //套餐必点
    3. private String hamburger;
    4. private String chips;
    5. //套餐选点
    6. private String chicken;
    7. private String cola;
    8. private String pizza;
    9. public KFC(String hamburger,String chips){
    10. this.hamburger = hamburger;
    11. this.hamburger = chips;
    12. }
    13. public void setChicken(String chicken) {
    14. this.chicken = chicken;
    15. }
    16. public void setCola(String cola) {
    17. this.cola = cola;
    18. }
    19. public void setPizza(String pizza) {
    20. this.pizza = pizza;
    21. }
  • 抽象建造者(builder)

    1. public abstract class Builder {
    2. abstract Builder setChicken();
    3. abstract Builder setCola();
    4. abstract Builder setPizza();
    5. abstract KFC getKFC();
    6. }
  • 具体建造者(ConcreteBuilder)

    1. public class ConcreteBuilder extends Builder {
    2. KFC kfc;
    3. public ConcreteBuilder(String hamburger,String chips){
    4. kfc = new KFC(hamburger,chips);
    5. }
    6. @Override
    7. Builder setChicken() {
    8. kfc.setChicken("鸡腿");
    9. return this;
    10. }
    11. @Override
    12. Builder setCola() {
    13. kfc.setCola("可乐");
    14. return this;
    15. }
    16. @Override
    17. Builder setPizza() {
    18. kfc.setPizza("披萨");
    19. return this;
    20. }
    21. @Override
    22. KFC getKFC() {
    23. return kfc;
    24. }
    25. }
  • 测试

    1. public class BTest {
    2. public static void main(String[] args) {
    3. KFC kfc = new ConcreteBuilder("汉堡","薯条").setChicken().setCola().getKFC();
    4. }
    5. }

    如果不需要抽象建造者的角色来规定生产内容,那么代码到这里其实还有进一步的简化空间。

五、静态内部类链式建造者模式

  1. public class KFC {
  2. //套餐必点
  3. private String hamburger;
  4. private String chips;
  5. //套餐选点
  6. private String chicken;
  7. private String cola;
  8. private String pizza;
  9. //一定要有一个带有Builder参数的建造者
  10. private KFC(Builder builder) {
  11. this.hamburger = builder.hamburger;
  12. this.chips = builder.chips;
  13. this.chicken = builder.chicken;
  14. this.cola = builder.cola;
  15. this.pizza = builder.pizza;
  16. }
  17. //注意必须为静态内部类
  18. public static class Builder{
  19. //套餐必点
  20. private String hamburger;
  21. private String chips;
  22. //套餐选点
  23. private String chicken;
  24. private String cola;
  25. private String pizza;
  26. public Builder(String hamburger,String chips){
  27. this.hamburger = hamburger;
  28. this.chips = chips;
  29. }
  30. public Builder setChicken(){
  31. this.chicken = "小鸡腿";
  32. return this;
  33. }
  34. public Builder setCola(){
  35. this.cola = "小可乐";
  36. return this;
  37. }
  38. public Builder setPizza(){
  39. this.pizza = "小披萨";
  40. return this;
  41. }
  42. //生成一个产品
  43. public KFC getKFC(){
  44. return new KFC(this);
  45. }
  46. }
  47. }
  48. public class BuilderTest {
  49. public static void main(String[] args) {
  50. KFC kfc = new KFC.Builder("大汉堡", "小薯条").setChicken().setCola().getKFC();
  51. System.out.println(kfc);
  52. }
  53. }

六、建造者模式和抽象工厂模式的区别

通过上面的代码,发现普通的建造者模式和抽象工厂模式很像,在建造者模式中的builder角色很像超级工厂,然后contracterBuilder很像具体的工厂,都是规定了建造的内容。
区别:

  • 建造者模式有指导者这个角色,直接返回一个组装好的产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族
  • 建造者模式更适合复杂的产品构建
  • 可以将抽象工厂模式理解成汽车零件生产工厂,而建造者模式看出组装工厂