使用构建器模式最多设置一次值 [英] Set a value at most once with the builder pattern

查看:115
本文介绍了使用构建器模式最多设置一次值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在使用生成器模式时,Java中是否存在标准做法,以确保最多设置一次成员变量。我需要确保设置器被调用0或1次,但再也不能调用更多次。我想抛出某种 RuntimeException ,但是我担心同步问题以及这方面的最佳实践。

Is there a standard practice in Java, while using the builder pattern, to ensure that a member variable is set at most once. I need to make sure that the setter is called 0 or 1 times but never more. I would like to throw a RuntimeException of some type but am worried about synchronization issues as well as best-practices in this area.

推荐答案

如果用户以类似非法的方式调用方法,则引发异常不会出现错误可以描述,但并不十分优雅。构建器模式背后的想法是让用户编写流利的,可读的对象定义,而编译时安全是其中的重要部分。如果用户不能确定生成器即使编译成功也不会成功,则说明您正在引入用户现在需要了解和解释的其他复杂性。

There's nothing wrong with raising an exception if a user calls a method in an illegal way like you describe, but it's not terribly elegant. The idea behind the builder pattern is to let users write fluent, readable object definitions, and compile-time safety is a big part of that. If users can't be confident the builder will succeed even if it compiles, you're introducing additional complexity users now need to understand and account for.

有几个实现您所描述的方式的方法,让我们来探索一下:

There are a couple of ways to accomplish what you're describing, lets explore them:


  1. 只要让用户做自己想做的事

关于构建器的一件好事是,它们可以让您从同一构建器构造多个不同的对象:

One nice thing about builders is they can let you construct multiple different objects from the same builder:

List<Person> jonesFamily = new ArrayList<>();
Person.Builder builder = new Person.Builder().setLastName("Jones");

for(String firstName : jonesFamilyFirstNames) {
  family.add(builder.setFirstName(firstName).build());
}

我认为您有充分的理由禁止这种行为,但是我如果我不讲这个有用的把戏,那一定会被遗忘的。也许您不需要首先限制它。

I assume you have a good reason for forbidding this sort of behavior, but I'd be remiss if I didn't call out this useful trick. Maybe you don't need to restrict this in the first place.

引发异常

您建议提出一个例外,这肯定会起作用。就像我说的那样,我认为这不是最优雅的解决方案,但这是一种实现方式(使用 Guava 前提,以提高可读性):

You suggest raising an exception, and that will certainly work. Like I said, I don't think it's the most elegant solution, but here's one implementation (using Guava's Preconditions, for extra readability):

public class Builder {
  private Object optionalObj = null;
  // ...

  public Builder setObject(Object setOnce) {
    checkState(optionalObj == null, "Don't call setObject() more than once");
    optionalObj = setOnce;
  }
  // ...
}

一个 IllegalStateException ,因此,如果您不使用番石榴,则可以调用抛出新的IllegalStateException()(您应该... :))。假设您没有在线程之间传递构建器对象,则应该没有同步问题。如果是这样,则应该进一步思考为什么需要在不同线程中使用相同的生成器-这几乎肯定是一种反模式。

This raises an IllegalStateException, so you can just call throw new IllegalStateException() if you aren't using Guava (you should be... :) ). Assuming you're not passing builder objects around between threads, you should have no synchronization issues. If you are, you should put some further thought into why you need the same builder in different threads - that's almost surely an anti-pattern.

完全不提供该方法

这是防止用户调用您不希望他们使用的方法的最干净,最清晰的方法-首先不要提供它。相反,请覆盖构建器的构造器或 build()方法,以便他们可以选择在那时(而不是其他时间)传递值。这样,您可以明确保证每个构造的对象最多可以设置该值。

This is the cleanest, clearest way to prevent the user from calling a method you don't want them to - don't provide it in the first place. Instead, override either the builder's constructor or build() method so they can optionally pass the value at that time, but at no other time. This way you clearly guarantee the value can be set at most once per object constructed.

public class Builder {
  // ...

  public Obj build() { ... }
  public Obj build(Object onceOnly) { ... }
}


  • 使用不同的类型公开某些方法

    我实际上尚未执行此操作,它可能比其价值更令人困惑(特别是,您可能需要使用自动绑定的通用(针对 Builder )中的方法,但是在我写作,对于某些用例可能非常明确。将受限制的方法放在构建器的子类中,该方法将返回父类型,因此可以防止调用方重新调用该方法。一个示例可能会有所帮助,如果那没有意义:

    I haven't actually done this, and it might be more confusing than it's worth (in particular, you'll likely need to use a self-bounding generic for the methods in Builder), but it came to mind while I was writing and could be very explicit for certain use cases. Have your restricted method in a subclass of the builder, and that method returns the parent type, therefore preventing the caller from re-calling the method. An example might help, if that didn't make sense:

    public class Builder {
      // contains regular builder methods
    }
    
    public class UnsetBuilder extends Builder {
      public Builder setValue(Object obj) { ... }
    }
    
    // the builder constructor actually returns an UnsetBuilder
    public static UnsetBuilder builder() { ... }
    

    然后我们可以这样称呼:

    Then we can call something like:

    builder().setValue("A").build();
    

    但是,如果我们尝试调用,则会出现编译时错误:

    But we'd get a compile-time error if we tried to call:

    builder().setValue("A").setValue("B").build();
    

    因为 setValue()返回 Builder ,它缺少 setValue()方法,因此可以防止出现第二种情况。要做到完全正确(如果用户将 Builder 强制转换回 UnsetBuilder ,该怎么办?)一些努力会满足您的需求。

    because setValue() returns Builder, which lacks a setValue() method, therefore preventing the second case. This would be tricky to get exactly right (what if the user casts the Builder back to a UnsetBuilder?) but with some effort would do what you're looking for.

    这篇关于使用构建器模式最多设置一次值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆