生成器模式

我们在创建一个复杂的对象时,常常会遇到一个问题:对象的构造函数需要接收大量的参数,其中一些是必选的,而另一些是可选的。

比如你需要创建一个 Resume(简历)对象,其中包含姓名、年龄、教育背景等必选项,同时还可以有获奖情况、工作经验、公司名称等多个可选项。

如果通过构造函数来传递这些参数,可能会是这样:

class Resume {
    private String name;
    private int age;
    private String education;
    private String awards;
    private String workExperience;
    private String companyName;

    public Resume(String name, int age, String education, String awards,
            String workExperience, String companyName) {
        this.name = name;
        this.age = age;
        this.education = education;
        this.awards = awards;
        this.workExperience = workExperience;
        this.companyName = companyName;
    }
}

使用时就类似这样:

// Tom 没有工作经验、公司名称等信息
Resume resume1 = new Resume("Tom", 23, "Bachelor", "Some Award", null, null);

// Jack 有工作经验、公司名称等信息
Resume resume2 = new Resume("Jack", 30, "Master", null, "XX Engineer", "Company A");

这种方式可以完成对象的创建,但是问题也很明显:

1、构造函数的参数列表很长。

2、当某些可选参数不存在时,我们不得不传入 null,这使得代码非常难以阅读和维护。

3、如果需要为对象新增参数,就要修改构造函数,导致所有使用到这个构造函数的代码都需要新增这个参数。

为了解决这些问题,我们可以使用本文要介绍的生成器模式(Builder Pattern)

生成器模式是一种创建型设计模式,简单来说就是通过一个 Builder 类来一步一步地构建对象,最后再通过一个 build() 方法生成最终的对象。

我们来看下如何用生成器模式来改造 Resume 类的创建过程,首先创建一个 ResumeBuilder 类用于构建 Resume 对象:

class ResumeBuilder {
    private String name;
    private int age;
    private String education;
    private String awards;
    private String workExperience;
    private String companyName;

    public ResumeBuilder setBasicInfo(String name, int age, String education) {
        this.name = name;
        this.age = age;
        this.education = education;
        return this;
    }

    public ResumeBuilder setAwards(String awards) {
        this.awards = awards;
        return this;
    }

    public ResumeBuilder setWorkExperience(String workExperience) {
        this.workExperience = workExperience;
        return this;
    }

    public ResumeBuilder setCompanyName(String companyName) {
        this.companyName = companyName;
        return this;
    }

    public Resume build() {
        if (name == null || age == 0 || education == null) {
            throw new IllegalArgumentException("Name, age and education are required");
        }
        return new Resume(name, age, education, awards, workExperience, companyName);
    }
}

ResumeBuilder 类的关键在于它的每个方法都返回自身,也就是返回 Builder 对象本身,这样调用完一个方法后可以继续在同一个对象上调用下一个方法,形成链式调用:

Resume resume1 = new ResumeBuilder()
        .setBasicInfo("Tom", 23, "Bachelor")
        .setAwards("Some Award")
        .build();

Resume resume2 = new ResumeBuilder()
        .setBasicInfo("Jack", 30, "Master")
        .setWorkExperience("XX Engineer")
        .setCompanyName("Company A")
        .build();

通过这种方式,代码的可读性大大提高,每个方法名清楚地表明了在设置哪个属性。可选属性按需设置就好,不需要传一堆 null

点击运行看看效果:

上面这个简单的例子就展示了生成器模式,主要有以下几个关键角色:

  • 产品(Product):表示被构建的复杂对象,即上面的 Resume 类。
  • 生成器(Builder):即上面的 ResumeBuilder 类,它提供了链式调用方法来设置 Resume 类的各个字段,并提供一个 build() 方法来创建 Resume 对象。

上述例子是最简单常用的生成器模式,当然你也可以根据实际情况变通。

比如你可以把 Resume 类的数据字段和构造函数都设置为私有的,这样就能限制调用方只能通过 ResumeBuilder 来创建对象。

再比如,可以将 ResumeBuilder 作为 Resume 类的内部类并提供一个 builder() 方法创建生成器对象,这样可以让代码实现更紧凑简洁:

class Resume {
    // ...

    private static class ResumeBuilder {
        // ...
    }

    public static ResumeBuilder builder() {
        return new ResumeBuilder();
    }
}

// 这样使用
Resume resume = Resume.builder()
        .setBasicInfo("Tom", 23, "Bachelor")
        .setAwards("Some Award")
        .build();