generics和(超级?)类型令牌可以帮助构建类型安全的新闻聚合器吗?

我有这个基本的News界面

 interface News { String getHeader(); String getText(); } 

SportsNewsFinancialNews等具体方法一样,提供getStockPrice()getSport()等特定方法。 新闻打算发送到

 interface Subscriber { void onNews(N news); } 

问题是如何注册和维护订阅。 我尝试的第一种方法是使用中央Aggregator ,在Class对象和Set<Subscriber>之间保持映射,但很快这种方法显示不可行。 这是所需的API

 public class Aggregator { public  void subscribe(Subscriber subscriber) { // TODO somehow (super type token) extract N and // add the item to the set retrieved by getSubscribersFor() } public  void dispatch(N news) { for (Subscriber subscriber: getSubscribersFor(news.getClass())) { subscriber.onNews(news); } } private  Set<Subscriber> getSubscribersFor(Class k) { // TODO retrieve the Set for the specified key from the Map } } 

有什么替代品可以安全吗? Java可以解决这个问题吗? 我把这个小小的演示放在网上,以帮助你更好地理解问题的真正含义。

UPDATE

另一种方法是使Aggregator本身使用实际新闻类型进行参数化。 这没关系,除了它是一个鸡和蛋的问题:现在需要找到一种方法来检索聚合器。 在Java中,没有办法表达以下内容

 interface News { static Aggregator getAggregator(); } 
  • static方法不能abstract
  • 没有办法在类型参数中引用当前类型

这就是我要做的。 如果您可以使用Guava(Google编写和使用的Google库),我建议您先向下滚动并查看其他解决方案。

香草爪哇

首先,首先添加一个方法来获取订阅者的类:

 public interface Subscriber { void onNews(N news); Class getSupportedNewsType(); } 

然后在实施时:

 public class MySubscriber implements Subscriber { // ... public Class getSupportedNewsType() { return MyNews.class; } } 

在聚合器中,包括未键入键和值的映射:

 private Map, Set> subscribersByClass = ... ; 

另请注意,Guava有一个multimap实现,可以为您提供多个值的关键。 只需Google“Guava Multimap”即可找到它。

注册用户:

 public  void register(Subscriber subscriber) { // The method used here creates a new set and puts it if one doesn't already exist Set> subscribers = getSubscriberSet(subscriber.getSupportedNewsType()); subscribers.add(subscriber); } 

并派遣:

 @SuppressWarnings("unchecked"); public  void dispatch(N news) { Set> subs = subscribersByClass.get(news.getClass()); if (subs == null) return; for (Subscriber sub : subs) { ((Subscriber) sub).onNews(news); } } 

注意这里的演员。 这是安全的,因为register方法和Subscriber接口之间的generics的性质,只要没有人做出一些可笑的错误,例如raw-typing,例如implements Subscriber (没有generics参数)。 SuppressWarnings注释会抑制编译器对此演员的警告。

以及检索订阅者的私有方法:

 private Set> getSubscriberSet(Class clazz) { Set> subs = subscribersByClass.get(news.getClass()); if (subs == null) { subs = new HashSet>(); subscribersByClass.put(subs); } return subs; } 

您的private方法和字段不需要是类型安全的。 它不会引起任何问题,因为Java的generics是通过擦除实现的,所以这里的所有集合无论如何都只是一组对象。 试图使它们类型安全只会导致令人讨厌的,不必要的演员表,这与其正确性无关。

重要的是您的public方法是类型安全的。 在Subscriber中声明generics的方式和Aggregator上的公共方法,打破它的唯一方法是通过原始类型,如上所述。 简而言之,只要没有不安全的演员表或原始打字,每个通过注册的Subscriber都可以保证接受您注册的类型。


使用番石榴

或者,您可以看看Guava的EventBus 。 对于你想要做的事情,IMO会更容易。

Guava的EventBus类使用注释驱动的事件调度而不是接口驱动。 这很简单。 您将不再拥有Subscriber界面。 相反,您的实现将如下所示:

 public class MySubscriber { // ... @Subscribe public void anyMethodNameYouWant(MyNews news) { // Handle news } } 

@Subscribe注释向Guava的EventBus发出信号,它应该记住该方法以便进行调度。 然后注册它并发送事件,使用EventBus isntance:

 public class Aggregator { private EventBus eventBus = new EventBus(); public void register(Object obj) { eventBus.register(obj); } public void dispatch(News news) { eventBus.dispatch(news); } } 

这将自动找到接受news对象的方法并为您执行调度。 您甚至可以在同一个class级中多次订阅:

 public class MySubscriber { // ... @Subscribe public void anyMethodNameYouWant(MyNews news) { // Handle news } @Subscribe public void anEntirelyDifferentMethod(MyNews news) { // Handle news } } 

或者对于同一订户中的多种类型:

 public class MySubscriber { // ... @Subscribe public void handleNews(MyNews news) { // Handle news } @Subscribe public void handleNews(YourNews news) { // Handle news } } 

最后, EventBus尊重层次结构,因此如果您有一个扩展MyExtendedNews的类,例如MyExtendedNews ,那么调度MyExtendedNews事件也将传递给那些关心MyExtendedNews事件的事件。 接口也是如此。 通过这种方式,您甚至可以创建全局订阅者:

 public class GlobalSubscriber { // ... @Subscribe public void handleAllTheThings(News news) { // Handle news } } 

您需要将class参数发送到dispatch 。 以下内容为我编译,不确定是否符合您的需求:

 import java.util.Set; interface News { String getHeader(); String getText(); } interface SportsNews extends News {} interface Subscriber { void onNews(N news); } class Aggregator { public  void subscribe(Subscriber subscriber, Class clazz) { // TODO somehow (super type token) extract N and // add the item to the set retrieved by getSubscribersFor() } public  void dispatch(N item, Class k) { Set> l = getSubscribersFor(k); for (Subscriber s : l) { s.onNews(item); } } private  Set> getSubscribersFor(Class k) { return null; // TODO retrieve the Set for the specified key from the Map } } 

您可以通过Class.getGenericSuperclass/getGenericInterfaces()获取超类型的subscriber.getClass() ,然后检查它们以通过ParameterizedType.getActualTypeArguments()提取哪个N确实是

例如

 public class SportsLover implements Subscriber { void onNews(SportsNews news){ ... } } if subscriber is an instance of SportsLover Class clazz = subscriber.getClass(); // SportsLover.class // the super type: Subscriber Type superType = clazz.getGenericInterfaces()[0]; // the type arg: SportsNews Type typeN = ((ParameterizedType)superType).getgetActualTypeArguments()[0]; Class clazzN = (Class)typeN; 

这适用于简单的情况。

对于更复杂的情况,我们需要更复杂的类型算法。

一种方法可以使用TypeToken来保存N

  Type typeOfCollectionOfFoo = new TypeToken>(){}.getType() 

使代码工作

将你的class级宣布为

 public static class Aggregator 

将方法签名更改为

  private Set> getSubscribersFor() { 

你完成了。