Skip to content

值对象

POJO

以联系信息举例:

java
public class ContactInfo {
  private String name;

  private String mobile;

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

  public void setMobile(String mobile) {
    this.mobile = mobile;
  }

  public String getName() {
    return this.name;
  }

  public String getMobile() {
    return this.mobile;
  }
}

上面的 POJO 可以用来代表联系信息值对象。要创建一个新的值对象,代码如下:

java
var contactInfo = new ContactInfo();
contactInfo.setName("张三");
contactInfo.setMobile("12345678901");

lombok + POJO

getter setter 看着很啰嗦,借助于 project lombok,可以以一种妥协的方式改进一下,如下

java
@Getter
@Setter
public class ContactInfo {
  private String name;

  private String mobile;
}

为什么不直接使用裸的对象字段,如下:

java
public class ContactInfo {
  public String name;

  public String mobile;
}

因为这样不符合 java bean 规范,历史上有很多框架都依赖 java bean 规范。

不可变 POJO

值对象应该是不可变对象,但是上面的写法明显是可变的,通过不提供 setter 可以做到不可变。比如:

java
public class ContactInfo {
  private final String name;

  private final String mobile;

  public ContactInfo(String name, String mobile) {
    this.name = name;
    this.mobile = mobile;
  }

  public String getName() {
    return this.name;
  }

  public String getMobile() {
    return this.mobile;
  }
}

上面这种写法,是 java 里最标准的实现值对象的方式,也是我推荐的方式。当字段较多时,构造函数的参数列表太长,调用时必须正取顺序输入参数,非常不好用,对此,java 并没有好的办法来解决。

Builder 模式

通过采用 Builder 模式,可以近似达到效果。代码如下:

java
public class ContactInfo {
  private String name;

  private String mobile;

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

  public void setMobile(String mobile) {
    this.mobile = mobile;
  }

  public String getName() {
    return this.name;
  }

  public String getMobile() {
    return this.mobile;
  }

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

  public static class Builder {
    private String name;

    private String mobile;

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

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

    public ContactInfo build() {
      return new ContactInfo(this.name, this.mobile);
    }
  }
}

var contactInfo = ContactInfo.builder()
  .name("张三").mobile("12345678901")
  .build();

lombok + builder

实践中还是使用妥协产物 project lombok 吧,如下:

java
@Builder
public class ContactInfo {
  private String name;

  private String mobile;
}

以上的 builder 方式是 java 能做到的最好的程度了,但是还存在一个缺点,缺少编译期检查能力,只能做到运行期检查。比如:

java
var contactInfo = ContactInfo.builder()
  .name("张三")
  .build();

上面代码中,没有设置 mobile 属性,但是代码照样编译通过。

Record class

如果你使用较新的 java 语言版本,可以使用 record class 简化定义值对象,如下:

java
public record ContactInfo(
  String name,
  String mobile
)

减少了 getter setter 的声明,但是它不符合 bean 规范,依旧没有解决当构造函数参数过多时,很难使用的问题。 实践中,只有字段少的对象适用,字段多的时候,还不如用 lombok + builder