多个枚举与一个枚举

Multiple enums vs One enum

我在查看反应流规范的 Publisher (AsyncIterablePublisher.java) 的示例实现时偶然发现了一些我不明白为什么要这样做的东西。

static interface Signal {};
enum Cancel implements Signal { Instance; };
enum Subscribe implements Signal { Instance; };
enum Send implements Signal { Instance; };

现实一点,我不像写这篇文章的人那样是一个高级程序员,我确信这样做是有原因的。但我也无法解释为什么它会比这样做更好(我本来会这样做)。

enum Signal {
  Cancel,
  Subscribe,
  Send;
}

有人可以向我解释一下为什么它会更好吗?优点/缺点?

对于 AsyncIterablePublisher 中的用法类型,这两种形式是等效的,并且可以说后者,一个具有多个常量的枚举,更为自然。

其实前者的形式很少见。我可以看到一个支持使用它的论点(但这种情况很少见,这意味着这一点通常并不那么重要):当你在自己的枚举中定义每个常量时,你有机会定义不同的 methods/fields,例如如:

enum Cancel implements Signal { Instance; };
enum Send implements Signal { 
  Instance; 
  public void someSendSpecificMethod() { ... }
}

所以您现在可以 Send.Instance.someSendSpecificMethod()。很尴尬,很难得。

区别在于您可以在不更改原始枚举的情况下添加另一个 Signal 实例。您的代码可以使用 Signal 实例,并且可以提供不同的类型。与 类 的界面相同 - 为您提供灵活性,但仅在您需要时才有用。在你的情况下,我没有看到太多用处,因为接口是空的,实现它的枚举中没有任何内容。

您可以查看此站点以获得带有接口的枚举的良好示例:

http://www.selikoff.net/2011/06/15/java-enums-can-implement-interfaces/

不要太严格,这是我对这段代码的解释。让我们称呼反应流的所有者 Roland。

起初 Roland 需要一个通用接口 inboundSignals

static interface Signal {};

ConcurrentLinkedQueue<Signal> inboundSignals = new ConcurrentLinkedQueue<Signal>();

CancelSubscribeSend 这样的信号总是具有相同的目的,它们是不可变的并且经常出现,所以将它们实现为 Joshua Bloch's Singleton 是个好主意:

enum Cancel    implements Signal { Instance; };
enum Subscribe implements Signal { Instance; };
enum Send      implements Signal { Instance; };

另一种方法与您的建议和我最喜欢的类似:

enum CommonSignals implements Signal{

    Cancel {
        @Override
        void debug() {
            System.out.println("Cancel");
        }
    },

    Subscribe {
        @Override
        void debug() {
            System.out.println("Subscribe");
        }
    },

    Send {
        @Override
        void debug() {
            System.out.println("Send");
        }
    };

    abstract void debug();

    [...] some other methods I could need in the future
}

如您所见,这是一个不同的实现。但想法是一样的——Signal as singleton

我们继续前进并找到这段代码:

static final class Request implements Signal {
    final long n;
    Request(final long n) { // every Request has different value of n
        this.n = n;
    }
};

因为 inboundSignals 可以包含多个 Request 对象,所以不可能将这种类型的信号实现为单例。因此它不能是 CommonSignals 的成员或实现为 enum.


结论

Roland 使用了多种可能性中的一种来实现单例。我认为这更多是一个品味问题。

因此,由于我不知道应该批准哪个答案,所以我给这段代码的作者发了邮件,询问他对此有何看法。这是对话(块来自他。普通文本是我的):

Hi Michael,

那么只有一个枚举有什么区别呢?你不会有三个单例,但仍然有三个唯一定义的对象。:

Sure, that could be done, but then I'd have to invent a name for it (like ConcreteSignal), my chosen encoding avoids that :)

enum Signal {
  Cancel, Subscribe, Send;
}

我认为这也是线程安全的,如果你真的想要共享接口,例如你可以与对象共享,你可以这样做:

enum ConcreteSignal implements Signal {
  Cancel,
  Subscribe,
  Send;
}

You could definitely do as you have done above, however I'd argue that they [Cancel, Subscribe, Send] are not more concrete than Request (which is also a signal): https://github.com/reactive-streams/reactive-streams-jvm/blob/v1.0.0/examples/src/main/java/org/reactivestreams/example/unicast/AsyncIterablePublisher.java#L49

Also, that could also encourage to use the methods of Enum to enumerate all possible Signals, which it couldn't, since Request is not a Signal. Makes sense?

事实上,在 Scala 中,您更愿意使用对象来完成它,但在 Java 中,很少看到像您的实现这样的东西。我很好奇我在 Java 中遗漏了一些巧妙的功能。但是,如果我理解正确的话,那是因为您更喜欢该实现,因为它更接近您的首选语言 Scala?

I guess there's a bit of that to it, but the singleton-as-enum-pattern in Java is quite well-known, so the only "weird" thing would be to have a distinct "enum" for each of Cancel, Subscribe and Send, but as I explained previously, since Request cannot be encoded like that I opted for the more "Scala-y" version.

So if "Request" would not need any parameters, I'd done as you suggested: enum Signal { Cancel, Request, Subscribe, Send }

谜团解开了:-)。

我认为使用枚举接口的唯一原因是某些信号的数据与其他信号不同,因此他需要扩展枚举类型以将数据附加到它。