建造者模式实现及使用场景--java

概述

建造者模式是一种创建型设计模式,主要目的在于将一个复杂的对象的创建分解为多个简单的步骤,然后逐步构建。
下面以一个资源池配置类ResourcePollConfig来进行说明。这个资源池配置类中有以下几个成员变量:

成员变量解释是否必填默认值
name资源名称没有
maxTotal最大总资源数量8
maxIdle最大空闲资源数量8
minIdle最小空闲资源数量0

使用构造函数

maxTotal、maxIdle、minIdle并不是必填项,所以创建对象的时候,参数传递null表示默认值

public class ResourcePoolConfig {
	//默认值
    private static final int DEFAULT_MAX_TOTLE = 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_TOTLE;
    private int maxIdle = DEFAULT_MAX_IDLE;
    private int minIdle = DEFAULT_MIN_IDLE;

    public ResourcePoolConfig(String name,Integer maxTotal,Integer maxIdle,Integer minIdle){
        if(name == null || name == ""){
            throw new IllegalArgumentException("name不能为空");
        }
        this.name = name;

        if(maxTotal != null){
            if(maxTotal <= 0){
                throw new IllegalArgumentException("maxTotal应该为正");
            }
            this.maxTotal = maxTotal;
        }

        if(maxIdle != null){
            if(maxTotal < 0){
                throw new IllegalArgumentException("maxIdle不能为负数");
            }
            this.maxIdle = maxIdle;
        }

        if(minIdle != null){
            if(minIdle < 0){
                throw new IllegalArgumentException("minIdle不能为负数");
            }
            this.minIdle = minIdle;
        }
    }
}

目前只有4个可配置项,还比较能够接受,如果配置项变得更多会存在问题,
存在的问题在于:

  • 参数过多导致代码可读性变差
  • 使用构造函数的时候可能搞错参数顺序,导致参数可能传递错误

使用set()函数

可以使用set()函数赋值成员变量,构造函数只保留必填项来解决这个问题

public class ResourcePoolConfig {
    private static final int DEFAULT_MAX_TOTLE = 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_TOTLE;
    private int maxIdle = DEFAULT_MAX_IDLE;
    private int minIdle = DEFAULT_MIN_IDLE;

    public ResourcePoolConfig(String name){
        if(name == null || name == ""){
            throw new IllegalArgumentException("name不能为空");
        }
        this.name = name;
    }

    public void setMaxTotal(int maxTotal) {
        if(maxTotal <= 0){
            throw new IllegalArgumentException("maxTotal应该为正");
        }
        this.maxTotal = maxTotal;
    }

    public void setMaxIdle(int maxIdle) {
        if(maxTotal < 0){
            throw new IllegalArgumentException("maxIdle不能为负数");
        }
        this.maxIdle = maxIdle;
    }

    public void setMinIdle(int minIdle) {
        if(minIdle < 0){
            throw new IllegalArgumentException("minIdle不能为负数");
        }
        this.minIdle = minIdle;
    }
}

使用用例如下:

ResourcePoolConfig config = new ResourcePoolConfig("config");
config.setMaxTotal(16);
config.setMaxIdle(8);

使用起来代码在可读性和易用性上提高了。但仍存在问题

  • 如果必填项有很多,那么就面临上述使用构造函数一样的问题,set()方法并没有完全解决此问题,需要说明的是,必填项是不能通过set()方法设置的,如果通过set()方法设置就不能校验必填项是否被给出。
  • 如果配置项之间存在约束条件,比如此例中maxIdle要小于等于maxTotal,minIdle要小于等于maxIdle。这时使用set()方法设置变量,校验的逻辑就无处安放。同样的,如果存在依赖关系也是如此
  • 如果希望配置类对象是不可变的,创建之后就不能修改内部属性值。那么就不应该暴露set()方法。

建造者模式

针对上述问题,就可以使用建造者模式。
将校验逻辑放到Builder类中,先创建建造者,并通过set()方法设置建造者的变量值,然后在使用build()方法真正创建对象之前,做集中的校验,校验通过后才会创建对象。除此之外可以将ResourcePoolConfig的构造函数设置为私有。这样就只能通过建造者创建对象。

public class ResourcePoolConfig {
    private String name;
    private int maxTotal;
    private int maxIdle;
    private int minIdle;
	//私有化构造函数,只能通过Builder创建对象
    private ResourcePoolConfig(Builder builder){
        this.name = builder.name;
        this.maxTotal = builder.maxTotal;
        this.maxIdle = builder.maxIdle;
        this.minIdle = builder.minIdle;
    }
	//也可以将Builder设计成独立的非内部类
    public static class Builder{
        private static final int DEFAULT_MAX_TOTLE = 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_TOTLE;
        private int maxIdle = DEFAULT_MAX_IDLE;
        private int minIdle = DEFAULT_MIN_IDLE;

        public ResourcePoolConfig build(){
            //属性之间的关系可以放在这里集中校验
            if(name == null || name == ""){
                throw new IllegalArgumentException("name不能为空");
            }
            if(maxIdle > maxTotal){
                throw new IllegalArgumentException("maxIdle不能大于maxTotal");
            }
            if(minIdle > maxIdle){
                throw new IllegalArgumentException("minIdle不能大于maxIdle");
            }
            return new ResourcePoolConfig(this);
        }

        public Builder setName(String name) {
            if (name == null || name == "") {
                throw new IllegalArgumentException("name不能为空");
            }
            this.name = name;
            return this;
        }

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

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

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

建造者模式里面使用了链式调用的技巧,设置变量时返回自身便于用户使用
使用用例如下:

ResourcePoolConfig config = new ResourcePoolConfig.Builder()
		.setName("")
		.setMaxTotal(16)
		.setMaxIdle(10)
		.setMinIdle(12)
		.build();

可以看到简洁了不少,同时兼顾了校验逻辑。但缺点是代码有点重复,ResourcePoolConfig中的成员变量要在Builder中重新定义一遍。
实际上如果并不是很关心对象是否可变,在业务上也没有特别的校验逻辑,比如数据库映射的entity对象,可以直接暴露set()方法来设置值。

与工厂模式区别

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

建造者模式使用场景总结

  1. 如果必填属性很多,为了代码的可读性和易用性,使用建造者模式
  2. 如果类的属性之间存在一定的依赖关系或者约束条件,为了能够对依赖关系和约束条件进行集中校验,可以使用建造者模式
  3. 如果希望创建不可变对象,也就不能暴露set()方法。可以使用建造者模式,然后私有化构造函数。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值