代码中常见的建造者模式

建造者模式(Bulider)

百度百科的定义是:建造者模式是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

网上搜索了下,大部分文章都会举例出建造者模式的四种角色:
1、产品(Product):具体生产器要构造的复杂对象
2、抽象生成器(Bulider):抽象生成器是一个接口,创建一个产品各个部件的接口方法,以及返回产品的方法
3、具体建造者(ConcreteBuilder):按照自己的产品特性,实现抽象建造者对应的接口
4、指挥者(Director):创建一个复杂的对象,控制具体的流程

看到这一堆的定义就脑阔疼,我们结合一个简单的例子来理一理。

/**
 * 模拟一个简单的数据库连接池对象
 */
public class SimpleDataSource {

    //数据库连接基本信息
    private String url;
    private String username;
    private String password;

    //连接池数量配置
    private Integer initialSize;
    private Integer maxSize;

    public SimpleDataSource() {
    }

    public SimpleDataSource(String url, String username, String password, Integer initialSize, Integer maxSize) {
        this.url = url;
        this.username = username;
        this.password = password;
        this.initialSize = initialSize;
        this.maxSize = maxSize;
    }

    @Override
    public String toString() {
        return "SimpleDataSource{" +
                "url='" + url + '\'' +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", initialSize=" + initialSize +
                ", maxSize=" + maxSize +
                '}';
    }

    /**
     * 获取建造者
     * @return
     */
    public static Builder builder(){
        return new Builder();
    }

    /**
     * 建造者类
     */
    public static class Builder{
        //必填部分
        private String url;
        private String username;
        private String password;
        //选填部分(有默认值)
        private Integer initialSize = 10;
        private Integer maxSize = 1000;

        public Builder setUrl(String url) {
            this.url = url;
            return this;
        }

        public Builder setUsername(String username) {
            this.username = username;
            return this;
        }

        public Builder setPassword(String password) {
            this.password = password;
            return this;
        }

        public Builder setInitialSize(Integer initialSize) {
            this.initialSize = initialSize;
            return this;
        }

        public Builder setMaxSize(Integer maxSize) {
            this.maxSize = maxSize;
            return this;
        }

        public SimpleDataSource build() {
            //校验下必填部分
            if (url == null) {
                throw new RuntimeException("url必填");
            }
            if (username == null) {
                throw new RuntimeException("username必填");
            }
            if (password == null) {
                throw new RuntimeException("password必填");
            }
            //创建对象
            return new SimpleDataSource(url, username, password, initialSize, maxSize);
        }
    }

}

我们模拟定义了一个数据库连接池SimpleDataSource,并简单的设置了几个参数(链接地址、账户、密码、初始链接个数、最大链接个数),这个SimpleDataSource类就是定义中的(1产品);在SimpleDataSource中,定义了一个Builder的静态内部类,这个类用于接收一些参数,并最终通过build方法构建出一个SimpleDataSource对象。这个静态内部类对应的就是定义中的(2和3建造者),只是我们把接口省略了(见过的绝大部分代码都没有定义接口,在这种场景下,已经是静态内部类了,而且是一对一配套的,确实也没必要定义接口);下方代码就是一个使用建造者创建一个对象的使用样例,对应的是定义中的(4指挥者),也就是我们实际使用代码的地方。

SimpleDataSource dataSource = SimpleDataSource.builder()
        .setUrl("url")
        .setUsername("username")
        .setPassword("password")
        .setInitialSize(1)
        .setMaxSize(100000)
        .build();
System.out.println(dataSource);

通过这个简单的例子可以看出,我们可以通过新建一个Builder对象,将创建某个对象的操作逻辑拆分出来,拆分出来有如下好处:
1、可以少调用几个配置,其他的属性使用默认值(对象也可以有默认值,好处不是很明显)
2、保证构建出来的对象是可用的,比如可以在build方法中增加检验,校验必填参数,也就是可以增加一些额外的逻辑
3、最明显的一个是,我们可以链式的调用set方法,比较方便

针对于3,虽然说多了链式调用,但是却要多一堆builder的代码,好像有点得不偿失?
所以,如果我们只是为了想要链式调用,在这里其实可以使用lombok的@Builder注解快速的让lombok帮我们创建出建造者。

在日常开发中,还有哪些常见的地方使用到了建造者模式呢?
1、lombok的@Builder注解
实现了链式调用设置属性。
2、StringBuilder类
StringBuilder我理解也是一种建造者模式,只是他是为了解决性能的问题。StringBuilder用于连接大量的字符串,通过append方法直接需要连接的字符串转换成字符数组存储起来,最后通过toString方法将字符数组转成String对象,在连接的过程中,减少了大量的String对象的创建,增加了性能。
3、Caffeine等本地缓存

Cache<Key, MyObject> cache = Caffeine.newBuilder()
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .maximumSize(10_000)
    .build();

本地缓存一般都支持配置最大数据量,过期策略等,一般也都是使用建造者模式进行构建。

总结

一个比较浅的理解:建造者模式就是将原本一个全参的构造函数,拆分成几个函数进行配置,将对象和构造过程分离(一个最简单最具象的理解,仅供参考,不一定正确)

使用场景:
1、隔离复杂对象的创建和使用,相同的方法,不同执行顺序,产生不同事件结果
2、多个部件都可以装配到一个对象中,但产生的运行结果不相同
3、产品类非常复杂或者产品类因为调用顺序不同而产生不同作用
4、初始化一个对象时,参数过多,或者很多参数具有默认值
5、Builder模式不适合创建差异性很大的产品类
6、产品内部变化复杂,会导致需要定义很多具体建造者类实现变化,增加项目中类的数量,增加系统的理解难度和运行成本
6、需要生成的产品对象有复杂的内部结构,这些产品对象具备共性

相关代码:https://gitee.com/lqccan/blog-demo/tree/master/DesignPattern/src/main/java/com/example/demo/builder


觉得内容还不错?打赏个钢镚鼓励鼓励!!👍