同学你根本不懂 Builder 设计模式!

作者:明明如月学长, CSDN 博客专家,蚂蚁集团高级 Java 工程师,《性能优化方法论》作者、《解锁大厂思维:剖析《阿里巴巴Java开发手册》》、《再学经典:《EffectiveJava》独家解析》专栏作者。

热门文章推荐

一、背景

在日常开发中,发现有些同学喜欢用 lombok 的 @Builder 注解,主要原因是喜欢使用链式编程。
但如果一个方法里面多个对象都使用 builder 模式,每个对象构建都放在一行使用,代码过长;如果每个属性设置都滑换行,占的行数就很多,导致可读性降低。

// 都放在一行,代码过宽
Person person = new Person.Builder().name("bob").age(23).gender("male").height(180).weight(75).build();

// 每个方法换行,占的行数过多
Person person = new Person.Builder()
    .name("bob")
    .age(23)
    .gender("male")
    .height(180)
    .weight(75)
    .build();

Builder 设计模式除了链式编程以外,还有啥好处吗?还有哪些副作用?使用的正确方式是怎样的?

同学  你根本不懂 Builder 设计模式!.png

二、Builder 模式

2.1 概念

builder 设计模式是一种将一个复杂对象的构建与其表示分离的方法,使得同样的构建过程可以创建不同的表示。
这种模式可以解决当一个类的构造函数参数过多,而且有些是可选的时候,使用构造器或者JavaBean模式会带来的问题,比如代码量大,灵活性低,对象状态不一致等。

Builder 设计模式的实现方法是在目标类中创建一个静态内部类 Builder,然后将目标类中的参数都复制到Builder类中。在目标类中创建一个私有的构造函数,参数为 Builder类型。在Builder类中提供设置各个参数的方法,并返回当前对象。最后在 Builder类中提供一个 build方法,用来创建目标类的实例,并将各个参数赋值给目标类。

2.2 Builder 模式的优缺点

Builder 设计模式的优点有:

  • 可以将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。
  • 可以将类的构造函数中的可选参数分离出来,使用setter的方式进行初始化,非常的灵活。
  • 可以使用链式调用,属性连续设置,看起来简洁,易于理解。
  • 可以隔离复杂对象的创建和使用,隐藏了产品的内部结构和实现细节。
  • 可以对产品进行更精细的控制,只需调用需要的构造步骤即可。

Builder 设计模式的缺点有:

  • 产品组成部分必须相同,限制了使用范围。
  • 如果产品内部变化复杂,会增加更多的具体建造者类,增加了系统的复杂度和运行成本。
  • 需要创建额外的Builder接口和具体建造者类,增加了代码量。
  • 与抽象工厂模式相比,不能很好地处理产品族的问题。

2.3 代码示例

一个简单的例子是用 builder 设计模式来创建一个 Computer类的对象。Computer类有一些必需的属性,如cpuram,以及一些可选的属性,如 usbCountkeyboard
使用 Builder 设计模式,我们可以在 Computer类中创建一个静态内部类 Builder,然后在 Builder类中提供设置各个属性的方法,并返回当前对象。最后在 Builder类中提供一个 build方法,用来创建Computer类的实例,并将各个属性赋值给 Computer类。
代码示例如下:

public class Computer {
    
    
  //必需的属性
  private String cpu;
  private String ram;
  //可选的属性
  private int usbCount;
  private String keyboard;

  //私有的构造函数,参数为Builder类型
  private Computer(Builder builder) {
    
    
    this.cpu = builder.cpu;
    this.ram = builder.ram;
    this.usbCount = builder.usbCount;
    this.keyboard = builder.keyboard;
  } 
    
    //静态内部类Builder
  public static class Builder {
    
    
    //必需的属性
    private String cpu;
    private String ram;
    //可选的属性
    private int usbCount;
    private String keyboard;

    //设置必需的属性的构造函数
    public Builder(String cpu, String ram) {
    
    
      this.cpu = cpu;
      this.ram = ram;
    }

    //设置可选的属性的方法,并返回当前对象
    public Builder setUsbCount(int usbCount) {
    
    
      this.usbCount = usbCount;
      return this;
    }
    
      public Builder setKeyboard(String keyboard) {
    
    
      this.keyboard = keyboard;
      return this;
    }

    //创建Computer类的实例,并将各个属性赋值给Computer类
    public Computer build() {
    
    
      return new Computer(this);
    }
  }
}

使用这种方式,我们可以灵活地创建不同配置的 Computer 对象,例如:

//创建一个只有cpu和ram的Computer对象
Computer computer1 = new Computer.Builder("Intel Core i7", "16GB").build();

//创建一个有cpu、ram、usbCount和keyboard的Computer对象
Computer computer2 = new Computer.Builder("AMD Ryzen 9", "32GB")
                     .setUsbCount(4)
                     .setKeyboard("Logitech")
                     .build();

三、lombok 的 @Builder 注解

3.1 基本认识

lombok 的 @Builder 注解是一种实现 builder 设计模式的方式。
lombok 的 @Builder 注解可以生成一个构造器类,通过这个类你可以使用链式调用的方式来初始化你的对象。例如:

@Data
@Builder
public class User {
    
    
  private Integer id;
  private String name;
  private String address;
}

User 编译后的代码:

// 使用 @Builder 生成的代码
public class User {
    
    
  //三个属性
  private Integer id;
  private String name;
  private String address;

  //无参构造函数
  public User() {
    
    
  }

  //全参构造函数
  public User(Integer id, String name, String address) {
    
    
    this.id = id;
    this.name = name;
    this.address = address;
  }
// 省略其他方法
    
//builder()方法,创建构建器类的新实例
  public static UserBuilder builder() {
    
    
    return new UserBuilder();
  }

  //内部静态类UserBuilder,用于构建User对象
  public static class UserBuilder {
    
    
    //三个属性
    private Integer id;
    private String name;
    private String address;

    //包私有的无参构造函数
    UserBuilder() {
    
    
    }
//id属性的setter方法,返回构建器本身
    public UserBuilder id(Integer id) {
    
    
      this.id = id;
      return this;
    }

    //name属性的setter方法,返回构建器本身
    public UserBuilder name(String name) {
    
    
      this.name = name;
      return this;
    }

    //address属性的setter方法,返回构建器本身
    public UserBuilder address(String address) {
    
    
      this.address = address;
      return this;
    }
      //build()方法,调用User类的全参构造函数,并返回User对象
    public User build() {
    
    
      return new User(id, name, address);
    }
  }

用法

User user = User.builder()
                .id(1)
                .name("张三")
                .address("北京")
                .build();

lombok 的 @Builder 注解可以让你省去写构造器类和各个属性的 setter 方法的麻烦。

3.2 副作用和注意事项

(1)如果你在类上使用了 @Builder 注解,那么你需要手动添加一个无参构造函数,否则有些序列化框架需要通过 newInstance 构造对象时会报错。
(2)如果你在类上使用了 @Builder 注解,那么你不能再在构造函数或方法上使用 @Builder 注解,否则会导致重复生成构造器类。
(3)如果你想给某个属性设置一个默认值,那么你需要在属性上使用 @Builder.Default 注解,否则默认值会被忽略。
(4)如果你想让子类继承父类的属性,那么你需要在子类的全参构造函数上使用 @Builder 注解,并且在父类上使用 @AllArgsConstructor 注解,否则子类的构造器类不会包含父类的属性。

四、总结

日常开发中有些同学喜欢用 @Builder ,我们在使用的时候不仅要享受它链式编程的好处,还要特别注意它的副作用。
Builder 设计模式的好处不仅是链式编程,更重要的是,可以通过 Builder 模式的构造方法来控制必传参数,还可以在设置参数方法或者在 build 方法中进行必传参数和参数合法性校验等。
当参数较少时,直接使用构造方法可能比 Builder 模式更简洁。


创作不易,如果本文对你有帮助,欢迎点赞、收藏加关注,你的支持和鼓励,是我创作的最大动力。

猜你喜欢

转载自blog.csdn.net/w605283073/article/details/129809062