generics和(超级?)类型令牌可以帮助构建类型安全的新闻聚合器吗?
我有这个基本的News
界面
interface News { String getHeader(); String getText(); }
和SportsNews
和FinancialNews
等具体方法一样,提供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() {
你完成了。