生成器模式& failling

时间:2017-03-08 15:49:45

标签: java solid-principles builder-pattern

考虑以下课程:

@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类实现,只允许从它的构建器构建新实例,这也适当地遵循构建器模式(如果我错了,请纠正我)。

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

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

是否可以从EmailVO的构造函数中删除验证并让构建器处理它?<​​/ strong>考虑在这种情况下构造函数是private使它对这个班级的外部不可见。

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

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

2 个答案:

答案 0 :(得分:2)

您似乎正在使用Project Lombok来消除大量的样板。也许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();

答案 1 :(得分:1)

考虑到您的EmailVO严格用于持有值, 我建议你尽量减少setter和构造函数中的登录(@Christopher Schneider评论的变体)。

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

关于“使用错误值构建”问题,请将该验证添加到构建器。

EmailVO验证可能包括“这是一个有效构建的电子邮件地址”。

此外,不要将构建器传递给EmailVO对象,只需拥有构建器使用的包访问构造函数。确保构建器与EmailVO对象位于同一个包中。