【问题标题】:Builder pattern & failling建造者模式和失败
【发布时间】:2017-03-09 00:03:56
【问题描述】:

考虑以下类:

@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),构建器可能会在没有初始化所需参数的情况下尝试构建,而不是让对象的构造器失败,这可能会删除许多构造函数内部的空检查。在示例代码中,如果lead 字段为null,则此验证可能会引发异常,而不是让EmailVO 的构造函数检查完整性,尽管这是构造函数的责任。

是否可以从 EmailVO 的构造函数中删除验证并让构建器处理它? 考虑在这种情况下构造函数是 private 使其不可见这个类的外面。

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

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

【问题讨论】:

  • 考虑到您的EmailVO 严格用于保存变量,我会说它应该具有零逻辑。
  • 在您的 EmailVO 构造函数中,考虑将 x = builder.x; if (x == null) {} 替换为 x = Objects.requireNonNull(builder.x);
  • @ChristopherSchneider 好点。 VO 对象应该仅用于携带值,而不是用于验证它们,但是让构建器执行此类验证不是违反 SOLID 吗?有人可能会说,您可以更不用说所有需要的验证都发生在一个孤立的方法中(在 Controller 等内部),但这将允许构建器构建部分构造的对象,这是否违反构建器模式?

标签: java solid-principles builder-pattern


【解决方案1】:

看起来您正在使用Project Lombok 来消除大量样板文件。也许immmutables 会很合适。

Immutables 在编译时通过注解处理器创建样板文件,并且有一种方便的方法来使用 fluent 构建器创建 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();

【讨论】:

  • 这肯定会解决我的问题,但我仍然想知道在这种情况下,对于 SOLID 最好做什么。无论如何,很高兴了解 Immutables。谢谢!
【解决方案2】:

考虑到您的EmailVO 严格用于保存值, 我建议您最小化设置器和构造器中的登录(@Christopher Schneider 注释的变体)。

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

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

EmailVO 验证可能包括诸如“这是一个有效构造的电子邮件地址”之类的内容。

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

【讨论】:

  • 如果你想要验证,你可以向 EmailVO 对象添加验证方法。 当然,当我说验证时,我的意思是检查是否存在所需的参数,而不是语义和一致性验证。但是关于关于“使用错误值构建”的问题,将该验证添加到构建器。这正是我想要做的,但这不是针对 SOLID 吗?这是我不确定的。
  • 我看不出建造者会在哪里破坏 SOLID。具体来说,构建器的职责是构造一个有效的EmailVO。这意味着构建器有责任防止构造错误的EmailVO 对象。
  • 建造者是否负责防止构建坏对象?它不负责为对象构造聚合参数吗?正如你所说,我是这样读的:构建器负责聚合参数以构建对象以防止构造该对象的格式错误的实例
  • 建造者声称“I R MAKE GUD STUFF”或“我会生产出一个好物件”。这使得建造者有责任生产一个好的对象。在这种情况下很好,包括有效值。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-13
  • 2010-12-10
  • 1970-01-01
  • 2010-11-18
相关资源
最近更新 更多