设计模式之建造者模式

建造者模式同样是创建型设计模式的一种,用于解决复杂对象的创建问题。

为什么需要建造者模式

为了得到一个对象,通常情况下我们调用构造方法然后再配合 set 方法做一些初始化工作即可。假定我们现在有一个传感器,传感器的名称和类型不能为空、客户ID可选、如果客户ID不为空时安装时间也不能为空,反应到类的代码如下。

public class Sensor {

    private String name;

    private String type;

    private Integer customerId;

    private Date installTime;

    public Sensor(String name, String type) {
        this(name, type, null);
    }

    public Sensor(String name, String type, Integer customerId) {
        this(name, type, customerId, null);
    }

    public Sensor(String name, String type, Integer customerId, Date installTime) {
        if (name == null || name.isEmpty()) {
            throw new IllegalArgumentException("名称不能为空");
        }
        if (type == null || type.isEmpty()) {
            throw new IllegalArgumentException("类型不能为空");
        }

        if (customerId != null) {
            if (installTime != null && installTime.compareTo(new Date()) > 0) {
                throw new IllegalArgumentException("安装时间有误");
            }
        }
        this.name = name;
        this.type = type;
        this.customerId = customerId;
        this.installTime = installTime;
    }
}

为了满足传感器各属性的限制,我们定义了三个构造方法,为了复用代码,我们的实现中参数少的构造方法会调用参数多的构造方法。当前只有三个构造方法还可以接受,如果以后需要再次扩展,如温度传感器需要配置报警的最低温度和最高温度,我们还需要为构造方法增加新的参数,具体如下。

public class Sensor {

    private String name;

    private String type;

    private Integer customerId;

    private Date installTime;

    private Double lower;

    private Double upper;

    public Sensor(String name, String type, Integer customerId, Date installTime, Double lower, Double upper) {
        ... 省略部分代码
        if ((lower != null && upper == null) || (upper != null && lower == null)) {
            throw new IllegalArgumentException("报警温度有误");
        }
        if (lower != null && upper != null && lower > upper) {
            throw new IllegalArgumentException("报警温度有误");
        }
        this.name = name;
        this.type = type;
        this.customerId = customerId;
        this.installTime = installTime;
        this.lower = lower;
        this.upper = upper;
    }
}

发现问题了吗?类的构造方法参数不断的增加,最终导致可读性极差,很容易传入错误的参数,使用者必须阅读相关注释或代码才能确实使用哪个构造方法。

那怎么解决这个问题呢?按照通常的思路,调用构造方法后再调 set 方法,我们只把传感器必须的属性作为构造方法参数,其他的属性通过 set 方法设置即可。代码如下。

        Sensor sensor = new Sensor("温度传感器", "温度");
        sensor.setInstallTime(new Date());
        sensor.setCustomerId(1);

但是问题又来了,我们是需要进行参数校验的,对于上述的场景设置了客户ID后则必须设置安装时间,我们无法保证设置安装时间方法、设置客户ID方法的调用顺序,也就是说对于有依赖关系的属性现在我们无法进行参数校验。怎么办呢?为了保证构造方法参数不断扩展又需要进行参数校验,建造者模式就出现了。

如何实现一个建造者模式

使用建造者模式解决上述问题,代码如下。

public class Sensor {

    private String name;

    private String type;

    private Integer customerId;

    private Date installTime;

    private Double lower;

    private Double upper;

    private Sensor(Build build) {
        this.name = build.name;
        this.type = build.type;
        this.customerId = build.customerId;
        this.installTime = build.installTime;
        this.lower = build.lower;
        this.upper = build.upper;
    }


    public static class Build {

        private String name;

        private String type;

        private Integer customerId;

        private Date installTime;

        private Double lower;

        private Double upper;


        public String getName() {
            return name;
        }
        public Build setName(String name) {
            this.name = name;
            return this;
        }

        public String getType() {
            return type;
        }
        public Build setType(String type) {
            this.type = type;
            return this;
        }

        public Integer getCustomerId() {
            return customerId;
        }
        public Build setCustomerId(Integer customerId) {
            this.customerId = customerId;
            return this;
        }

        public Date getInstallTime() {
            return installTime;
        }
        public Build setInstallTime(Date installTime) {
            this.installTime = installTime;
            return this;
        }

        public Double getLower() {
            return lower;
        }
        public Build setLower(Double lower) {
            this.lower = lower;
            return this;
        }

        public Double getUpper() {
            return upper;
        }
        public Build setUpper(Double upper) {
            this.upper = upper;
            return this;
        }

        public Sensor build() {
            if (this.name == null || this.name.isEmpty()) {
                throw new IllegalArgumentException("名称不能为空");
            }
            if (this.type == null || this.type.isEmpty()) {
                throw new IllegalArgumentException("类型不能为空");
            }

            if (this.customerId != null) {
                if (this.installTime != null && this.installTime.compareTo(new Date()) > 0) {
                    throw new IllegalArgumentException("安装时间有误");
                }
            }
            if ((this.lower != null && this.upper == null) || (this.upper != null && this.lower == null)) {
                throw new IllegalArgumentException("报警温度有误");
            }
            if (this.lower != null && this.upper != null && this.lower > this.upper) {
                throw new IllegalArgumentException("报警温度有误");
            }
            Sensor sensor = new Sensor(this);
            return sensor;
        }
    }
}

使用方此时创建传感器类实例的代码如下。

Sensor sensor = new Sensor.Build().setName("温度传感器").setType("温度").setCustomerId(1).setInstallTime(new Date()).build();

我们将设置对象属性的代码转移到了建造者类中,然后在建造者类的 build 方法中完成了参数的校验。

总结

建造者模式用于解决复杂对象创建的问题,针对构造方法参数过长和具有依赖关系的参数校验问题,将参数设置的代码转移到建造者类,并通过建造者类的 build 方法完成存在依赖关系的参数校验。实现一个建造者模式通常的步骤如下。

  1. 在实体类中创建一个静态内部类 XxxBuilder,属性和实体类保持一致。
  2. 实体类提供一个私有的仅有一个类型为静态内部类的参数。
  3. 静态内部类的 setter 方法返回静态内部类实例,便于链式调用。
  4. 静态内部类提供 build 方法,用于参数校验及实例化实体类。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大鹏cool

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值