Java笔记··By/蜜汁炒酸奶

漫谈建造者模式

1. 对象创建时赋值方式

1.1 构造器赋值

构造器赋值,适合参数少时,参数一多可能出现如下问题:

  • 构造函数的参数列表会变得很长,代码在可读性和易用性上都会变差。
  • 在使用构造函数的时候,我们就容易搞错各参数的顺序,传递进错误的参数值,导致非常隐蔽的 bug。

1.2 setter函数赋值

解决构造器赋值的最简单的方式就是用setter函数来给成员变量赋值,以替代冗长的构造函数。

  • 对于必填项, 放到构造函数中设置,强制创建类对象的时候就要填写。
  • 对于选填项,通过 set() 函数来设置,让使用者自主选择填写或者不填写。

1.2.1 不足

使用setter时依旧可能存在不足之处:

  • 对于必填项,如果必填的配置项有很多,把这些必填配置项都放到构造函数中设置,那构造函数就又会出现参数列表很长的问题。如果我们把必填项也通过 set() 方法设置,那校验这些必填项是否已经填写的逻辑就无处安放了
  • 假设配置项之间有一定的依赖关系(比如设置了其中一个,就必须显式设置其余的一个或多个),或者配置项之间有一定的约束条件(如成员变量a和成员变量b必须小于成员变量c),若依旧按setter方式赋值,那这些配置项之间的依赖关系或者约束条件的校验逻辑就无处安放了
  • 若希望类的对象属于不可变对象,即对象在创建好之后,就不能再修改内部的属性值时,就不可以暴露该类的setter函数

1.3 建造者模式赋值

为了解决上面的问题,可以使用建造者模式。

Builder 模式,中文翻译为建造者模式或者构建者模式,也有人叫它生成器模式。

相关文章
Java设计模式学习笔记—建造者模式

  • 可以把校验逻辑放置到 Builder 类中,
  • 先创建建造者,并且通过 set() 方法设置建造者的变量值,
  • 然后在使用 build() 方法真正创建对象之前,做集中的校验,校验通过之后才会创建对象
  • 将真正对象的构造函数改为 private 私有权限,且不提供setter函数,就只能通过构造者来创建,创建出来的对象就是不可变对象了。
  • 使用建造者模式创建对象,还能避免对象存在无效状态。
public class ResourcePoolConfig3 {
    private String name;
    private int maxTotal;
    private int maxIdle;
    private int minIdle;
    private ResourcePoolConfig3(Builder builder) {
        this.name = builder.name;
        this.maxTotal = builder.maxTotal;
        this.maxIdle = builder.maxIdle;
        this.minIdle = builder.minIdle;
    }

    //...省略getter方法...
    // 将Builder类设计成了ResourcePoolConfig的内部类。
    // 也可以将Builder类设计成独立的非内部类ResourcePoolConfigBuilder。
    public static class Builder {
        private static final int DEFAULT_MAX_TOTAL = 8;
        private static final int DEFAULT_MAX_IDLE = 8;
        private static final int DEFAULT_MIN_IDLE = 0;

        private String name;
        // 最大总资源数量
        private int maxTotal = DEFAULT_MAX_TOTAL;
        // 最大空闲资源数量
        private int maxIdle = DEFAULT_MAX_IDLE;
        // 最小空闲资源数量
        private int minIdle = DEFAULT_MIN_IDLE;

        public ResourcePoolConfig3 build() {
            // 校验逻辑放到这里来做,包括必填项校验、依赖关系校验、约束条件校验等
            if (StringUtils.isBlank(name)) {
                throw new IllegalArgumentException("...");
            }
            if (maxIdle > maxTotal) {
                throw new IllegalArgumentException("...");
            }
            if (minIdle > maxTotal || minIdle > maxIdle) {
                throw new IllegalArgumentException("...");
            }
            return new ResourcePoolConfig3(this);
        }

        public Builder setName(String name) {
            if (StringUtils.isBlank(name)) {
                throw new IllegalArgumentException("...");
            }      this.name = name;
            return this;
        }

        public Builder setMaxTotal(int maxTotal) {
            if (maxTotal <= 0) {
                throw new IllegalArgumentException("...");
            }
            this.maxTotal = maxTotal;
            return this;
        }

        public Builder setMaxIdle(int maxIdle) {
            if (maxIdle < 0) {
                throw new IllegalArgumentException("...");
            }
            this.maxIdle = maxIdle;
            return this;
        }

        public Builder setMinIdle(int minIdle) {
            if (minIdle < 0) {
                throw new IllegalArgumentException("...");
            }
            this.minIdle = minIdle;
            return this;
        }
    }


    public static void main(String[] args) {
        // 这段代码会抛出IllegalArgumentException,因为minIdle>maxIdle
        ResourcePoolConfig3 config = new ResourcePoolConfig3.Builder()
                .setName("dbconnectionpool")
                .setMaxTotal(16)
                .setMaxIdle(10)
                .setMinIdle(12)
                .build();
    }
}
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
77
78
79
80
81
82
83
84
85

1.3.1 对象存在无效状态

若用setter方式创建一个长方形,在使用第一个setter创建高度(或宽度)时,尚未设置另一个属性,此时便处于无效状态。

Rectangle r = new Rectange(); // r is invalid
r.setWidth(2); // r is invalid
r.setHeight(3); // r is valid
1
2
3
  • 成员变量少时,可以使用构造函数一次性初始化好所有的成员变量。
  • 如果构造函数参数过多,我们就需要考虑使用建造者模式,先设置建造者的变量,然后再一次性地创建对象,让对象一直处于有效状态。

若并不是很关心对象是否有短暂的无效状态,也不是太在意对象是否是可变的,直接暴露 set() 方法来设置类的成员变量值是完全没问题的。

使用建造者模式来构建对象,代码实际上是有点重复的,ResourcePoolConfig 类中的成员变量,要在 Builder 类中重新再定义一遍。

2. 与工厂模式的区别

建造者模式是让建造者类来负责对象的创建工作,工厂模式是由工厂类来负责对象创建的工作。

  • 工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。
  • 建造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化”地创建不同的对象。

3. 优点与不足

该部分来自《Head First设计模式》,有的地方可能过于抽象或官方语言,仅作相关参考。

3.1 优点

  • 将一个复杂对象的创建过程封装起来。
  • 允许对象通过多个步骤来创建,并且可以改变过程(工厂模式只有一个步骤)。
  • 向客户隐藏产品内部的表现。
  • 产品的实现可以被替换,因为客户只看到一个抽象的接口,

2.2 用途与不足

  • 经常被用来创建组合结构。
  • 与工厂模式相比,采用建造者模式创建对象的客户,需要具备更多的领域知识。

参考资料

预览
Loading comments...
0 条评论

暂无数据

example
预览