这就是我要做的。如果您可以使用 Guava(由 Google 编写和使用的 Google 库),我建议您先向下滚动并查看其他解决方案。
香草爪哇
首先,首先添加一个从订阅者那里获取类的方法:
public interface Subscriber<N extends News> {
void onNews(N news);
Class<N> getSupportedNewsType();
}
然后在实施时:
public class MySubscriber implements Subscriber<MyNews> {
// ...
public Class<MyNews> getSupportedNewsType() {
return MyNews.class;
}
}
在您的聚合器中,包含未键入键和值的映射:
private Map<Class<?>, Set<Subscriber<?>> subscribersByClass = ... ;
另请注意,Guava 有一个 multimap 实现,它将为您执行多个值的这个键。只需谷歌“Guava Multimap”,您就会找到它。
要注册订阅者:
public <N extends News> void register(Subscriber<N> subscriber) {
// The method used here creates a new set and puts it if one doesn't already exist
Set<Subscriber<?>> subscribers = getSubscriberSet(subscriber.getSupportedNewsType());
subscribers.add(subscriber);
}
并派遣:
@SuppressWarnings("unchecked");
public <N extends News> void dispatch(N news) {
Set<Subscriber<?>> subs = subscribersByClass.get(news.getClass());
if (subs == null)
return;
for (Subscriber<?> sub : subs) {
((Subscriber<N>) sub).onNews(news);
}
}
注意这里的演员表。register
这将是安全的,因为方法和接口之间的泛型性质Subscriber
,只要没有人做一些荒谬的错误,比如原始类型implements Subscriber
(没有泛型参数)。注释禁止来自编译器的有关此强制转换的SuppressWarnings
警告。
以及您检索订阅者的私有方法:
private Set<Subscriber<?>> getSubscriberSet(Class<?> clazz) {
Set<Subscriber<?>> subs = subscribersByClass.get(news.getClass());
if (subs == null) {
subs = new HashSet<Subscriber<?>>();
subscribersByClass.put(subs);
}
return subs;
}
您的private
方法和字段不需要是类型安全的。无论如何它不会引起任何问题,因为 Java 的泛型是通过擦除实现的,所以这里的所有集合无论如何都只是一组对象。试图让它们类型安全只会导致讨厌的、不必要的强制转换,这与它的正确性无关。
重要的是您的public
方法是类型安全的。泛型的声明方式Subscriber
和公共方法 on Aggregator
,打破它的唯一方法是通过原始类型,就像我上面所说的那样。简而言之,只要没有不安全的强制转换或原始类型,就可以保证Subscriber
每个传递给 register的类型都接受您正在注册的类型。
使用番石榴
或者,您可以查看 Guava 的EventBus
. IMO,对于您正在尝试做的事情,这会更容易。
Guava 的EventBus
类使用注解驱动的事件分派而不是接口驱动。这真的很简单。您将不再有Subscriber
界面。相反,您的实现将如下所示:
public class MySubscriber {
// ...
@Subscribe
public void anyMethodNameYouWant(MyNews news) {
// Handle news
}
}
@Subscribe
注释向 Guava 发出信号,它应该在EventBus
以后记住该方法以进行调度。然后注册它并调度事件,使用一个EventBus
实例:
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
对象的方法并为您进行调度。您甚至可以在同一个课程中多次订阅:
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
尊重层次结构,所以如果你有一个扩展的类MyNews
,例如MyExtendedNews
,那么调度MyExtendedNews
事件也将传递给那些关心MyNews
事件的人。接口也是如此。这样,你甚至可以创建一个全局订阅者:
public class GlobalSubscriber {
// ...
@Subscribe
public void handleAllTheThings(News news) {
// Handle news
}
}