设计模式(四)建造者模式 Builder

  • Builder:

  《Effective Java》 第2条:遇到多个构造器参数时要考虑用构建器。

  建造者模式(Builder Pattern),也称生成器模式,定义如下:

  将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

  Separate the construction of a complex object from its representation so that the same construction process can create different representation.

  • 什么时候使用 Builder:

  我们常常会看见这种情况:我需要一个初始化一个复杂对象,在初始化的同时完成参数的赋值工作。

  • 数据模型:
@Data
@EqualsAndHashCode
@ToString
public class PolicyCommon {

    private String code;
    private String category;
    private String name;
    private String inceptionDate;

    public PolicyCommon() {
        super();
    }

    public PolicyCommon(String code, String category, String name) {
        super();
        this.code = code;
        this.category = category;
        this.name = name;
    }
}
  • 模型分析:

  PolicyCommon 带有4个参数,假定只有 inceptionDate不需要在创建的时候赋值。

  剩余3个参数参与初始化的工作,那么这个对象初始化之后的状态可能性为:2^3=8。

  • 传统的解决方案1——使用无参构造器,然后依次调用 set() 方法:
    @Test
    void testPolicyCommon1() {
        PolicyCommon policy = new PolicyCommon();
        policy.setName("Gerrard");
        policy.setCode("11123768");
        policy.setCategory("Engineer");
        System.out.println(policy);
    }

  这种方法,将构造对象的过程拆分成多个动作,是存在风险的。

  因为对象不是一次性构造完成,使得对象在构造过程中存在状态不一致的情况。

  期间有 this 指针溢出的风险,阻碍了这个对象称为不可变对象的可能。在多线程条件下,需要额外的工作才能保证线程安全。

  • 传统的解决方案2——使用重叠参数的构造器:
    @Test
    void testPolicyCommon2() {
        PolicyCommon policy = new PolicyCommon("11123768", "Engineer", "Gerrard");
        System.out.println(policy);
    }

  这样做的劣势在于:

  1. 从调用者的角度来说,属性的意义不明显,很容易将参数位置颠倒了,但是编译器并没有报错,例如: new PolicyCommon("Engineer", "Gerrard", "11123768");
  2. 如果我只想初始化一个属性,如 code,那么构造器的调用会有许多冗余参数。例如:new PolicyCommon("11123768", null, null);
  •  Builder 给出的解决方案:

  使用一个静态内部类 Builder,Builder 持有需要动态生成的属性。

  Builder 为每一个属性,提供 set() 方法,但是与通常的set() 方法不同,这些 set() 方法的返回值是 Builder 本身。

  Builder 提供一个 build() 方法,返回类型为外部类。

  外部类中,提供一个参数为 Builder 对象的构造器,且将其的权限设置为 private,如此一来,构造器的唯一途径就是 build() 方法。

  • 代码:
@Data
@EqualsAndHashCode
@ToString
public class PolicyBuilder {

    private String code;
    private String category;
    private String name;
    private String inceptionDate;

    private PolicyBuilder(Builder builder) {
        code = builder.code;
        category = builder.category;
        name = builder.name;
    }

    public static class Builder {

        private String code;
        private String category;
        private String name;

        public Builder setCode(String code) {
            this.code = code;
            return this;
        }

        public Builder setCategory(String category) {
            this.category = category;
            return this;
        }

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

        public PolicyBuilder build() {
            return new PolicyBuilder(this);
        }
    }
}
  • 调用 Builder:
    @Test
    void testPolicyBuilder() {
        PolicyBuilder policy = new PolicyBuilder.Builder().setName("Gerrard").setCode("11123768").build();
        System.out.println(policy);
    }

  可以看出,Builder 初始化的过程中,对参数选择更加自由,而且它不会使对象处于不同的状态。

  劣势在于冗长的内部类代码,以及构建过程中会增加一个 Builder 对象。

  • 使用 Lombok 一键 Builder:

  细心的朋友,在最初的 PolicyCommon 的数据模型中,会发现三个注解,这是 Lombok 框架的功能(org.projectlombok,License = MIT)。

  @Data,为类的每一个属性,自动生成 get() 和 set() 方法。

  @EqualsAndHashCode,为类生成 equals() 和 hashCode() 方法。

  @ToString,为类生成 toString() 方法。

  除此之外,Lombok 还提供了 @Builder 的注解,自动为类提供了 Builder 的功能。

  • Lombok Builder:
@Data
@EqualsAndHashCode
@ToString
public class PolicyBuilderLombok {

    private String code;
    private String category;
    private String name;
    private String inceptionDate;

    @Builder
    public PolicyBuilderLombok(String code, String category, String name) {
        super();
        this.code = code;
        this.category = category;
        this.name = name;
    }
}
    @Test
    void testPolicyBuilderLombok() {
        PolicyBuilderLombok policy = new PolicyBuilderLombok.PolicyBuilderLombokBuilder()
                .name("Gerrard").code("11123768").build();
        System.out.println(policy);
    }
  • 解析 @Builder:

  检查元注解:@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR})

  这表明这个注解可以加在:类、构造器、方法(不推荐加在方法上)。

  @Builder 加在类上 <==> ,在类的全参构造器上加上 @Builder。

  @Builder 加在构造器上,这个构造器的入参就是内部类 Builder 的动态参数。

原文地址:https://www.cnblogs.com/jing-an-feng-shao/p/7552615.html