【问题标题】:NullObjectPattern and the Comparable interfaceNullObjectPattern 和 Comparable 接口
【发布时间】:2014-04-23 14:02:40
【问题描述】:

我遇到的问题之前已经有人问过了:How to implement an interface with an enum, where the interface extends Comparable?

但是,没有一个解决方案可以解决我的确切问题,即:

我有一个值对象,类似于BigDecimal。有时该值不会用真实对象设置,因为该值尚不知道。所以我想用Null Object Pattern 来表示这个对象没有定义的时间。这一切都不是问题,直到我尝试让我的 Null 对象实现 Comparable 接口。这是一个 SSCCE 来说明:

public class ComparableEnumHarness {
  public static interface Foo extends Comparable<Foo> {
    int getValue();
  }

  public static class VerySimpleFoo implements Foo {
    private final int value;

    public VerySimpleFoo(int value) {
      this.value = value;
    }

    @Override
    public int compareTo(Foo f) {
      return Integer.valueOf(value).compareTo(f.getValue());
    }

    @Override
    public int getValue() {
      return value;
    }
  }

  // Error is in the following line:
  // The interface Comparable cannot be implemented more than once with different arguments:
  // Comparable<ComparableEnumHarness.NullFoo> and Comparable<ComparableEnumHarness.Foo>
  public static enum NullFoo implements Foo {
    INSTANCE;

    @Override
    public int compareTo(Foo f) {
      return f == this ? 0 : -1; // NullFoo is less than everything except itself
    }

    @Override
    public int getValue() {
      return Integer.MIN_VALUE;
    }
  }
}

其他问题:

  • 在实际示例中,这里有多个我称之为Foo 的子类。
  • 我可以通过让NullFoo 不是enum 来解决这个问题,但是我不能保证只有一个实例,即Effective Java Item 3, pg. 17-18

【问题讨论】:

  • 我不喜欢 NullObject 模式。尤其是因为它会把你引向这种东西。在您的程序中比较 NullObject 表示的未初始化对象是否有意义?
  • @Joffrey 这不是“未定义”,而是语义上的“零”。虽然这样想可能是解决这个问题的正确方法……"Once a problem is described in sufficient detail, its solution is obvious"
  • 我想用空对象模式来表示这个对象没有定义的时间。 -- 好吧,我以为你的意思是 undefined .但无论如何,Zero 不是null,也不是NullObject。你的班级是什么类型的?当它不是“null”时它应该代表什么?你说类似于 BigDecimal,所以我假设它是数字?
  • @Joffrey 这是一个逐渐更新的值,但它从零开始是一个合理的初始值。 undefined 不是正确的词,uninitialized 更好。
  • 好吧,你提出了 undefined,其实我自己也使用过 non-initialized ^^ 所以我的意思是,实际上是否有意义在比较中使用未初始化的对象?如果您希望它在语义上为 0,并且表现得如此,那么为什么不使用普通对象的 0 值呢?如果你以后需要它,你可以有一个标志说它没有被初始化。

标签: java enums comparable null-object-pattern


【解决方案1】:

我不推荐 NullObject 模式,因为我总是发现自己处于以下两种情况之一:

  • 像对象一样使用 NullObject 没有意义,应该保持null
  • NullObject 仅仅作为一个 NullObject 有太多的意义,它本身应该是一个真正的对象(例如,当它像一个全功能的默认值时)

根据我们在 cmets 中的讨论,在我看来,您的 NullObject 的行为与普通对象的 0 值非常相似。

我要做的实际上是使用 0(或任何更有意义的默认值),并在您确实需要知道它是否已初始化时放置一个标志。这样,您将需要考虑两件事:

  • 所有未初始化的值不会与我的解决方案共享同一个实例
  • 出于同样的原因,您现在可以稍后初始化您的对象,而无需创建新实例

这是我想到的那种代码:

public static class VerySimpleFoo implements Foo {
    private int value;
    private boolean initialized;

    public VerySimpleFoo() {
      this.value = 0; // whatever default value makes more sense
      this.initialized = false;
    }

    public VerySimpleFoo(int value) {
      this.value = value;
      this.initialized = true;
    }

    @Override
    public int compareTo(Foo f) {
      // possibly need some distinction here, depending on your default value
      // and the behavior you expect
      return Integer.valueOf(value).compareTo(f.getValue());
    }

    @Override
    public int getValue() {
      return value;
    }

    public void setValue(int value) {
      this.value = value;
      this.initialized = true;
    }

    public boolean isInitialized() {
      return initialized;
    }
}

【讨论】:

  • 将未初始化的实例与负值实例进行比较时会出现意外行为。 compareTo 方法需要改进。
  • @sp00m 感谢您指出这一点。在这种情况下,OP 可以轻松修改compareTo() 以处理此问题;)如有必要,我将编辑我的帖子。
  • @sp00m 在 SSCCE 中是的,实际上负值实例永远不会存在。事实上,零值实例永远不会存在,除非是 NullObject 情况
  • @durron597 好的,那么 Joffrey 的回答将按预期工作 ;)
【解决方案2】:

正如您所建议的,我相信一种解决方案是使用类而不是枚举:

public class NullFoo implements Foo {

    private NullFoo() {
    }

    public static final Foo INSTANCE = new NullFoo();

    @Override
    public int compareTo(Foo f) {
        return f == this ? 0 : -1;
    }

    @Override
    public int getValue() {
        return 0;
    }

}

这模仿了枚举行为,但它允许您实现 Foo 接口。由于私有构造函数,该类不可实例化,因此唯一可用的实例是可通过NullFoo.INSTANCE 访问的实例,它是线程安全的(感谢final 修饰符)。

【讨论】:

  • 仅供参考,我将我的 SSCCE 更改为 getValueInteger.MIN_VALUE,我在编写 SSCCE 的那部分很懒惰,但出于 compareTo 的目的,这在 SSCCE 版本中更有意义.我想这不是很“正确”:)
【解决方案3】:

问题是 Enum 已经原生实现了 Comparable,并且由于泛型只是一个糖代码,并且在编译后丢失,因此实际上您希望为同一个接口实现两次相同的方法。

对于 NullFoo,我会删除枚举,将其转换为类(如您所建议的),并使用私有构造函数进行最终的公共静态实例引用,(这不如使用枚举好,但在大多数情况下是可以接受的) .

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-03-17
    • 1970-01-01
    • 2011-01-14
    • 2020-08-01
    • 1970-01-01
    • 2011-10-31
    • 1970-01-01
    相关资源
    最近更新 更多