7

当我偶然发现一些我不明白为什么这样做的事情时,我正在查看反应流规范的 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;
}

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

4

5 回答 5

2

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

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

static interface Signal {};

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

Cancel,SubscribeSend总是具有相同目的的信号是不可变的并且非常频繁地出现,因此将它们实现为Joshua Bloch 的 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对象,因此无法将这种类型的 Signal 实现为 Singleton。因此它不能CommonSignals成为enum.


结论

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

于 2015-05-11T08:27:27.637 回答
1

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

实际上,前一种形式非常罕见。我可以看到一个支持使用它的论点(但如此罕见,这意味着这一点通常并不那么重要):当您在自己的枚举中定义每个常量时,您就有机会定义不同的方法/字段,例如:

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

所以你现在可以做Send.Instance.someSendSpecificMethod()。非常尴尬,非常难得。

于 2015-05-11T07:06:50.627 回答
1

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

您可以查看此站点以获取带有接口的枚举的一个很好的示例:

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

于 2015-05-11T07:15:29.130 回答
0

因此,由于我不知道哪些答案要批准,所以我邮寄了这段代码的作者,并询问了他对此的看法。这是对话(块来自他。普通文本是我的):

嗨迈克尔,

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

当然,这可以做到,但是我必须为它发明一个名称(如 ConcreteSignal),我选择的编码避免了这种情况:)

enum Signal {
  Cancel, Subscribe, Send;
}

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

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

您绝对可以像上面那样做,但是我认为它们 [Cancel, Subscribe, Send] 并不比 Request(这也是一个信号)更具体: https ://github.com/reactive-streams/反应流-jvm/blob/v1.0.0/examples/src/main/java/org/reactivestreams/example/unicast/AsyncIterablePublisher.java#L49

此外,这也可以鼓励使用 Enum 的方法来枚举所有可能的信号,但它不能,因为 Request 不是信号。说得通?

实际上,在 Scala 中,您宁愿使用 Object 来执行此操作,但在 Java 中,很少看到像您的实现这样的东西。我只是好奇我错过了 Java 中的一些简洁功能。但是,如果我的理解正确,那是因为您对该实现更满意,因为它更接近您首选的语言 Scala?

我想这其中有一点,但是 Java 中的单例枚举模式是众所周知的,所以唯一“奇怪”的事情就是为每个取消、订阅和订阅都有一个不同的“枚举”发送,但正如我之前解释的那样,由于请求不能像那样编码,所以我选择了更“Scala-y”的版本。

因此,如果“请求”不需要任何参数,我会按照您的建议完成: enum Signal { Cancel, Request, Subscribe, Send }

谜团已揭开 :-)。

于 2015-05-11T13:35:35.530 回答
0

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

于 2015-05-25T11:56:26.270 回答