建造者模式与失败

Builder pattern & failling

考虑以下 class:

@Getter
public class EmailVO {
    private final Long id;
    private final String firstName;
    private final String email;
    private final String address;

    @Slf4j
    @Component
    @Scope("prototype")
    public static class Builder {
        private Lead lead;

        private Long id;
        private String firstName;
        private String email;
        private String address;

        public Builder fromLead(Lead lead) {
            this.lead = lead;
            return this;
        }

        public EmailVO build() {
            if (lead == null) {
                log.error("Failed to build EmailVO: lead was not initialized");
                return new EmailVO(this);
            }

            User user = lead.getUser();
            id = user.getId();
            firstName = user.getFirstName();
            email = user.getEmail();
            address = user.getAddress();

            return new EmailVO(this);
        }
    }

    private EmailVO(Builder builder) {
        id = builder.id;
        firstName = builder.firstName;
        email = builder.email;
        address = builder.address;

        if (id == null ||
            firstName == null ||
            email == null ||
            address == null)
        {
            throw new IllegalStateException(); // Maybe some ohter Unchecked Exception would be better
        }
    }
}

据我所知,这将是一个正确的 VO class 实现,它只允许从它的构建器构建新实例,它也充分遵循构建器模式(如果我错了请纠正我) .

从 SOLID 的角度来看,此代码很好,因为构建器的唯一职责是聚合数据以构建 EmailVO,并且它的构造函数将负责只让它的有效实例生效。

现在,如果您关心代码的混乱和可读性(想象一个更大的 VO),当一个人试图在没有初始化必需参数的情况下构建而不是让对象的构造函数失败时,构建器可能会失败,这可能会潜在删除构造函数中的许多空检查。在示例代码中,如果 lead 字段是 null,此验证可能会抛出异常,而不是让 EmailVO 的构造函数检查完整性,尽管这是构造函数的责任。

是否可以从 EmailVO 的构造函数中删除验证并让构建器处理它? 考虑在这种情况下构造函数是private 使其在此 class.

外部不可见

这似乎与 SOLID 背道而驰,尽管如果构建器未验证所需的参数,它可能会失败,因为它的一个责任是聚合所需的数据以构建 EmailVO 实例。

然而,我想到的一个想法是将标志作为 EmailVO.Builder class 的成员字段,以指示它是否成功聚合了所需的参数,然后 EmailVO 的构造函数只能检查(并信任)这个标志。

看起来你正在使用 Project Lombok to eleminate a lot of boilerplate. Perhaps immmutables 会很合适。

Immutables 通过注解处理器在编译时创建样板文件,并且可以方便地使用流畅的构建器创建 pojo。

来自着陆页:

// Define an abstract value type
@Value.Immutable
public interface ValueObject {
  String name();
  List<Integer> counts();
  Optional<String> description();
}

// Then use generated immutable implementation
ValueObject valueObject =
    ImmutableValueObject.builder()
        .name("My value")
        .addCounts(1)
        .addCounts(2)
        .build();

考虑到您的 EmailVO 仅用于保存值, 我建议您最小化设置器和构造函数中的登录(@Christopher Schneider 评论的变体)。

如果你想要验证, 您可以向 EmailVO 对象添加验证方法。

关于 "build with bad values" 问题,将该验证添加到构建器中。

EmailVO 验证可能包括类似 "is this a validly constructed email address".

的内容

此外,不要将生成器传递给 EmailVO 对象,只需具有生成器使用的包访问构造函数即可。确保构建器与 EmailVO 对象在同一个包中。