问题背景:

Builder模式在很多地方都有用到,代码风格也比较简洁,但是对其深入的了解并不多,因此在参考其他博客的基础上写此文章。
首先先看我的实际应用到Builder模式的场景:
场景一:
这是在编写RPC简易框架时,需要输入服务端的配置信息时

RPC.Server server = new RPC.Builder(new Configuration())
                .setBindAddress("localhost")
                .setPort(8888)
                .setProtocol(RPCProtocol.class)
                .setInstance(new NNServer())
                .build();

场景二:
此处是编写ES的索引时

//创建插入类 Index   Builder中的参数表示要插入到索引中的文档,底层会转换Json格式的字符串,所以也可以将文档封装为样例类对象
    val index: Index = new Index.Builder(source)
      .index("movie_index_5")
      .`type`("movie")
      .id("1")
      .build()

问题分析

在上述两个场景中,我们可以看到Builder模式的应用,都是在初始化有多个参数的情境下,那么为什么要用Builder解决问题呢,他有什么优势,我们以一个例子讲解:

public class User {

    private final String firstName;     // 必传参数
    private final String lastName;      // 必传参数
    private final int age;              // 可选参数
    private final String phone;         // 可选参数
    private final String address;       // 可选参数
}

在这个类中,有些参数是必要的,而有些参数是非必要的。那么问题就来了,如何创建这个类的对象呢?
方法一(使用构造方法)
第一个构造方法只包含两个必需的参数,第二个构造方法中,增加一个可选参数,第三个构造方法中再增加一个可选参数,依次类推,直到构造方法中包含了所有的参数。

    public User(String firstName, String lastName) {
        this(firstName, lastName, 0);
    }

    public User(String firstName, String lastName, int age) {
        this(firstName, lastName, age, "");
    }

    public User(String firstName, String lastName, int age, String phone) {
        this(firstName, lastName, age, phone, "");
    }

    public User(String firstName, String lastName, int age, String phone, String address) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
        this.phone = phone;
        this.address = address;
    }

这样虽然可以运行,但是有以下缺点:
①参数多时将很麻烦
②对于只想传一个参数,且参数位置在后边,如传入address,那么只能采用最后一个构造方法,且需对前几个参数赋值,这是耗时且不必要的操作。
方法二(采用get,set方式)
我们同样可以根据JavaBean的习惯,设置一个空参数的构造方法,然后为每一个属性设置setters和getters方法

public class User {
    private String firstName;     // 必传参数
    private String lastName;      // 必传参数
    private int age;              // 可选参数
    private String phone;         // 可选参数
    private String address;       // 可选参数

    public User() {
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public int getAge() {
        return age;
    }

    public String getPhone() {
        return phone;
    }

    public String getAddress() {
        return address;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

这种方法看起来可读性不错,而且易于维护。作为调用者,创建一个空的对象,然后只需传入所需要的参数,但是仍有以下缺点:
①对象会产生不一致的状态。当你想要传入5个参数的时候,你必需将所有的setXX方法调用完成之后才行。然而一部分的调用者看到了这个对象后,以为这个对象已经创建完毕,就直接使用了,其实User对象并没有创建完成。
②类是可变的了,不可变类所有好处都不复存在。
此时,Builder模式便可以解决以上问题。

public class User {

    private final String firstName;     // 必传参数
    private final String lastName;      // 必传参数
    private final int age;              // 可选参数
    private final String phone;         // 可选参数
    private final String address;       // 可选参数

    private User(UserBuilder builder) {
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
        this.age = builder.age;
        this.phone = builder.phone;
        this.address = builder.address;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public int getAge() {
        return age;
    }

    public String getPhone() {
        return phone;
    }

    public String getAddress() {
        return address;
    }

    public static class UserBuilder {
        private final String firstName;
        private final String lastName;
        private int age;
        private String phone;
        private String address;

        public UserBuilder(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }

        public UserBuilder age(int age) {
            this.age = age;
            return this;
        }

        public UserBuilder phone(String phone) {
            this.phone = phone;
            return this;
        }

        public UserBuilder address(String address) {
            this.address = address;
            return this;
        }

        public User build() {
            return new User(this);
        }
    }
}

这种方式有以下优点:
①类的构造方法是私有的。也就是说调用者不能直接创建User对象。
②类的属性都是不可变的。所有的属性都添加了final修饰符,并且在构造方法中设置了值。并且,对外只提供getters方法。
③Builder的内部类构造方法中只接收必传的参数,并且该必传的参数适用了final修饰符。

因此,创建一个User类对象的代码为:

new User.UserBuilder("王", "小二")
                .age(20)
                .phone("123456789")
                .address("亚特兰蒂斯大陆")
                .build();

代码分析:

UserBuilder类是内部类,且被static修饰,则为静态内部类。它有以下特点:

1.静态内部类跟静态方法一样,只能访问静态的成员变量和方法,不能访问非静态的方法和属性,但是普通内部类可以访问任意外部类的成员变量和方法

2.静态内部类可以声明普通成员变量和方法,而普通内部类不能声明static成员变量和方法。

3.静态内部类可以单独初始化

4.外部类访问成员内部类的成员,需要“内部类.成员”或“内部类对象.成员”的方式

静态内部类使用场景一般是当外部类需要使用内部类,而内部类无需外部类资源,并且内部类可以单独创建的时候会考虑采用静态内部类的设计。

总结:

1.如果类的构造器或静态工厂中有多个参数,设计这样类时,最好使用Builder模式,特别是当大多数参数都是可选的时候。

2.如果现在不能确定参数的个数,最好一开始就使用构建器即Builder模式。

参考博客:

https://www.jianshu.com/p/e2a2fe3555b9
https://blog.csdn.net/czh500/article/details/83876425
https://blog.csdn.net/junehappylove/article/details/85236946

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐