原创

Builder建造者模式

建造者模式是日常开发中比较常见的设计模式,它的主要作用就是将复杂事物创建的过程抽象出来,该抽象的不同实现方式不同,创建出的对象也不同。通俗的讲,创建一个对象一般都会有一个固定的步骤,这个固定的步骤我们把它抽象出来,每个抽象步骤都会有不同的实现方式,不同的实现方式创建出的对象也将不同。举个常见的例子,想必大家都买过电脑,电脑的生产或者组装其实就是属于建造者模式,我们知道,电脑的生产都需要安装CPU、内存条、硬盘等元器件。我们可以把这个安装步骤抽象出来,至于到底装哪种CPU,比如i5还是i7就是对该抽象安装步骤的具体实现。

建造者模式?

建造者模式(Builder Pattern)使用多个简单的对象一步一步构建一个复杂的对象。

建造者模式主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定

Builder模式

建造者模式分为两种,一种为经典建造者模式,另一种为变种建造者模式

经典Builder模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kCfIdMxk-1607609422922)(/Users/lipan/app/typora-pic/image-20201210205916789.png)]

从上图可以看到,经典Buider模式中有四个角色:

  • Builder:给出一个抽象接口,以规范产品对象的各个组成成分的建造。这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的对象部件的创建。
  • ConcreteBuilder:实现Builder接口,针对不同的商业逻辑,具体化复杂对象的各部分的创建。 在建造过程完成后,提供产品的实例。
  • Director:调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。
  • Product:要创建的复杂对象。

1.首先创建一个Product对象:

 1public class Computer {
2
3    /*CPU*/
4    private String CPU;
5    /*内存*/
6    private String memory;
7    /*硬盘*/
8    private String hardDisk;
9    /*键盘*/
10    private String keyboard;
11    /*鼠标*/
12    private String mouse;
13
14    public String getCPU() {
15
16        return CPU;
17    }
18
19    public void setCPU(String CPU) {
20
21        this.CPU = CPU;
22    }
23
24    public String getMemory() {
25
26        return memory;
27    }
28
29    public void setMemory(String memory) {
30
31        this.memory = memory;
32    }
33
34    public String getHardDisk() {
35
36        return hardDisk;
37    }
38
39    public void setHardDisk(String hardDisk) {
40
41        this.hardDisk = hardDisk;
42    }
43
44    public String getKeyboard() {
45
46        return keyboard;
47    }
48
49    public void setKeyboard(String keyboard) {
50
51        this.keyboard = keyboard;
52    }
53
54    public String getMouse() {
55
56        return mouse;
57    }
58
59    public void setMouse(String mouse) {
60
61        this.mouse = mouse;
62    }
63
64    @Override
65    public String toString() {
66
67        return "Computer{" +
68                "CPU='" + CPU + '\'' +
69                ", memory='" + memory + '\'' +
70                ", hardDisk='" + hardDisk + '\'' +
71                ", keyboard='" + keyboard + '\'' +
72                ", mouse='" + mouse + '\'' +
73                '}';
74    }
75}

2. 创建一个抽象的电脑组装过程的Builder类:

 1public interface IComputerConfigBuilder {
2
3    /** * CPU */
4    void setCpu();
5
6    /** * 内存 */
7    void setMemory();
8
9    /** * 磁盘 */
10    void setHardDisk();
11
12    /** * 键盘 */
13    void setKeyboard();
14
15    /** * 鼠标 */
16    void setMouse();
17
18    /** * 电脑 * * @return */
19    Computer getComputer();
20}

电脑组装一般都需要安装CPU、内存条、硬盘、键盘鼠标等,我们把这一安装过程给抽象出来,也就是这里的ComputerConfigBuilder ,至于具体安装什么需要其实现类来实现,另外其中还定义了一个获取Conputer的方法。

3.有了抽象的组装过程,接下来我们就需要创建具体的实现类(ConcreteBuilder)

我们知道电脑一般都有低配版和高配版,不同配置,组装成的电脑自然就不一样。接下我们首先来创建一个低配版的套餐LowConfigBuilder ,让其实现ComputerConfigBuilder:

 1//i5的CPU、8G内存、500G硬盘、薄膜键盘和有线鼠标。
2public class LowConfigBuilder implements IComputerConfigBuilder {
3
4
5    private Computer mComputer;
6
7    public LowConfigBuilder() {
8
9        this.mComputer = new Computer();
10    }
11
12    @Override
13    public void setCpu() {
14
15        mComputer.setCPU("i5");
16    }
17
18    @Override
19    public void setMemory() {
20
21        mComputer.setMemory("8G");
22    }
23
24    @Override
25    public void setHardDisk() {
26
27        mComputer.setHardDisk("500G");
28    }
29
30    @Override
31    public void setKeyboard() {
32
33        mComputer.setKeyboard("薄膜键盘");
34    }
35
36    @Override
37    public void setMouse() {
38
39        mComputer.setMouse("有线鼠标");
40    }
41
42    @Override
43    public Computer getComputer() {
44
45        return mComputer;
46    }
47}

接着我们再创建一个高配版的套餐:


4.Director是建造者模式的核心,调用具体建造者来创建不通配置的电脑

 1public class Director {
2
3    private IComputerConfigBuilder mBuilder;
4
5    /**
6     * setBuilder来告诉需要什么配置电脑 * * @param builder
7     */

8    public void setBuilder(IComputerConfigBuilder builder) {
9
10        this.mBuilder = builder;
11    }
12
13    /**
14     * 一步步组装电脑
15     */

16    public void createComputer() {
17
18        mBuilder.setCpu();
19        mBuilder.setMemory();
20        mBuilder.setHardDisk();
21        mBuilder.setKeyboard();
22        mBuilder.setMouse();
23    }
24
25    /**
26     * 获取组装后的电脑 * * @return
27     */

28    public Computer getComputer() {
29
30        return mBuilder.getComputer();
31    }
32}

我们需要通过setBuilder来告诉他电脑需要什么配置,然后就可以通过createComputer来一步步组装电脑,组装完之后就可以调用getComputer方法来获取我们需要的电脑啦。

 1// 测试
2    @Test
3    public void buildPatternsV1Test() {
4
5        Director director = new Director();//创建装机人员
6        director.setBuilder(new LowConfigBuilder()); //告诉装机人员电脑配置,这里为低配版
7        director.createComputer(); //装机人员开始组装
8        Computer computer = director.getComputer(); //从装机人员获取组装好的电脑
9        System.out.print("电脑配置:" + computer.toString());  //查看电脑配置
10
11// director.setBuilder(new HighConfigBuilder());
12// director.createComputer();
13// Computer computer = director.getComputer();
14// System.out.print("电脑配置:" + computer.toString());
15    }

变种Builder模式

今天Boss突然跑过来扔了一个需求给你:需要创建一个不可变的Person对象,这个Person可以拥有以下几个属性:名字、性别、年龄、职业、车、鞋子、衣服、钱、房子。其中名字和性别是必须有的。

 1public class Person {
2
3    /*名字(必须)*/
4    private final String name;
5    /*性别(必须)*/
6    private final String gender;
7    /*年龄(非必须)*/
8    private final String age;
9    /*鞋子(非必须)*/
10    private final String shoes;
11    /*衣服(非必须)*/
12    private final String clothes;
13    /*钱(非必须)*/
14    private final String money;
15    /*房子(非必须)*/
16    private final String house;
17    /*汽车(非必须)*/
18    private final String car;
19    /*职业(非必须)*/
20    private final String career;
21
22    public Person(String name, String gender, String age, String shoes, String clothes, String money, String house, String car, String career) {
23
24        this.name = name;
25        this.gender = gender;
26        this.age = age;
27        this.shoes = shoes;
28        this.clothes = clothes;
29        this.money = money;
30        this.house = house;
31        this.car = car;
32        this.career = career;
33    }
34
35    public Person(String name, String gender) {
36
37        this(name, gender, nullnullnullnullnullnullnull);
38    }
39
40}

由于要创建出的Person对象是不可变的,所以你将类中的属性都声明为final的,然后定义了一个参数为所有属性的构造方法,又因为name和gender为必须项,所以你为了调用者方便又单独定义了一个参数为name和gender的构造方法。这样Person类就好了,你信心满满的把这个类提交给了Boss,Boss看了,还是很满意的,不过一段时间后,Boss向你反馈了一个问题,就是如果需要传入非必须属性的时候,这个构造方法调用起来不是很方便,因为这个构造方法参数太多了,很容易传错。你试了下,发现确实有这个问题,看来不能把参数全都都放在构造方法中,很快你想到了用set方法设置:

 1public class Person {
2
3    /*名字(必须)*/
4    private String name;
5    /*性别(必须)*/
6    private String gender;
7    /*年龄(非必须)*/
8    private String age;
9    /*鞋子(非必须)*/
10    private String shoes;
11    /*衣服(非必须)*/
12    private String clothes;
13    /*钱(非必须)*/
14    private String money;
15    /*房子(非必须)*/
16    private String house;
17    /*汽车(非必须)*/
18    private String car;
19    /*职业(非必须)*/
20    private String career;
21
22    public String getName() {
23
24        return name;
25    }
26
27    public void setName(String name) {
28
29        this.name = name;
30    }
31
32    public String getGender() {
33
34        return gender;
35    }
36
37    public void setGender(String gender) {
38
39        this.gender = gender;
40    }
41
42    public String getAge() {
43
44        return age;
45    }
46
47    public void setAge(String age) {
48
49        this.age = age;
50    }
51
52    public String getShoes() {
53
54        return shoes;
55    }
56
57    public void setShoes(String shoes) {
58
59        this.shoes = shoes;
60    }
61
62    ......
63
64}

如果要创建对象的话只用如下操作就行了:

1Person person = new Person();
2        person.setName("张三");
3        person.setAge("22");
4        person.setGender("男");
5        person.setCareer("程序员");
6        ......

这样看上去比较清晰了,只要创建一个对象,想要赋什么值set上去就可以了,不过你细细看了下,还是发现了不少的问题的,首先用这个set方法,违背了刚开始这个对象不可变的需求,其次用这种set方法一条一条赋值,逼格不够高,另外用这种方式很可能会得到一个不完整的Person对象,因为当你建完了Person对象,可能出于各方面的原因有些信息忘记set了,那么你得到的Person对象就不是你预期的对象。这时,你有点困惑了,只好打开谷歌一顿搜索,没想到还真的找到了解决办法,就是用下面这种变种的Builder模式:

 1public class Person {
2
3    /*名字(必须)*/
4    private final String name;
5    /*性别(必须)*/
6    private final String gender;
7    /*年龄(非必须)*/
8    private final String age;
9    /*鞋子(非必须)*/
10    private final String shoes;
11    /*衣服(非必须)*/
12    private final String clothes;
13    /*钱(非必须)*/
14    private final String money;
15    /*房子(非必须)*/
16    private final String house;
17    /*汽车(非必须)*/
18    private final String car;
19    /*职业(非必须)*/
20    private final String career;
21
22    private Person(Builder builder) {
23
24        this.name = builder.name;
25        this.gender = builder.gender;
26        this.age = builder.age;
27        this.shoes = builder.shoes;
28        this.clothes = builder.clothes;
29        this.money = builder.money;
30        this.house = builder.house;
31        this.car = builder.car;
32        this.career = builder.career;
33    }
34
35    public static class Builder {
36
37        private final String name;
38        private final String gender;
39        private String age;
40        private String shoes;
41        private String clothes;
42        private String money;
43        private String house;
44        private String car;
45        private String career;
46
47        public Builder(String name,String gender) {
48
49            this.name = name;
50            this.gender = gender;
51        }
52
53        public Builder age(String age) {
54
55            this.age = age;
56            return this;
57        }
58
59        public Builder car(String car) {
60
61            this.car = car;
62            return this;
63        }
64
65        public Builder shoes(String shoes) {
66
67            this.shoes = shoes;
68            return this;
69        }
70
71        public Builder clothes(String clothes) {
72
73            this.clothes = clothes;
74            return this;
75        }
76
77        public Builder money(String money) {
78
79            this.money = money;
80            return this;
81        }
82
83        public Builder house(String house) {
84
85            this.house = house;
86            return this;
87        }
88
89        public Builder career(String career) {
90
91            this.career = career;
92            return this;
93        }
94
95        public Person build(){
96
97            return new Person(this);
98        }
99    }

由于这个Person对象是不可变的,所以毫无疑问我们给他的所有属性都加了final修饰,当然如果没有不可变的需求也是可以不加的,然后在Person类中定义一个内部类Builder,这个Builder内部类中的属性要和Person中的相同,并且必须有的属性要用final修饰,防止这些属性没有被赋值,其他非必须的属性不能用final,因为如果加了final,就必须对其进行初始化,这样这些非必须的属性又变成必须的。然后内部类中定义了一个构造方法,传入必须有的属性。其他非必须的属性都通过方法设置,每个方法都返回Builder对象自身。最后定义了一个build方法,将Builder对象传入Person的私有构造方法,最终返回一个对象。

接下来我们来看下Person的创建:

1Person person = new Person.Builder("张三","男")
2                .age("12")
3                .money("1000000")
4                .car("宝马")
5                .build();

是不是看上去逼格瞬间提高了,非必须的属性可以根据需要任意设置,非常灵活,而且这样先设置属性再创建对象,最终获取的对象一定是你预期的完整对象,不会像用之前set的方法创建的对象可能还没有设置完全。好了,写完之后,你迫不及待的把这个Person类提交给了Boss,果然Boss对这种对象创建方式非常满意。

它使用了内部类的方式,将原来自己的类方法变成私有的,而后提供一个静态的内部类来创建对象。另外通过返回this对象来链式调用,本类同时为对象提供了一个默认值

Builder模式使用注意事项

  1. 客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
  2. 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象
  3. 可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程
  4. 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则
  5. 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不符合使用建造者模式,因此其使用范围受到一定的限制。
  6. 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,因此在这种情况下,要考虑是否选择建造者模式。
  7. 抽象工厂模式 VS 建造者模式 抽象工厂模式实现对产品家族的创建,一个产品的家族是这样的一系列产品;具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。

建造者模式与工厂模式类似,他们都是建造者模式,适用的场景也很相似。一般来说,如果产品的建造很复杂,那么请用工厂模式;如果产品的建造更复杂,那么请用建造者模式。

参考文章

  • https://www.jianshu.com/p/afe090b2e19c (大部分来源于此文章)
  • https://zhuanlan.zhihu.com/p/58093669
  • https://cloud.tencent.com/developer/article/1600768
  • http://www.manongjc.com/article/12059.html
正文到此结束
本文目录