这就是我要做的。如果您可以使用 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 有一个多重映射实现,可以为您执行此键到多个值的操作。只要谷歌“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;
}
Your private
方法和字段不需要类型安全。无论如何,它不会造成任何问题,因为 Java 的泛型是通过擦除实现的,所以这里的所有集合无论如何都只是一组对象。试图使它们类型安全只会导致令人讨厌的、不必要的强制转换,而这与其正确性无关。
What does问题是你的public
方法是类型安全的。泛型的声明方式Subscriber
和公共方法Aggregator
,打破它的唯一方法是通过原始类型,就像我上面所说的那样。简而言之,每一个Subscriber
传递给寄存器的是保证只要不存在不安全的强制转换或原始类型,就可以接受您注册的类型。
使用番石榴
或者,您可以看看 Guava 的EventBus
。 IMO,对于您想要做的事情来说,这会更容易。
Guava's EventBus
类使用注释驱动的事件调度而不是接口驱动。这真的很简单。你不会有一个Subscriber
界面不再了。相反,您的实现将如下所示:
public class MySubscriber {
// ...
@Subscribe
public void anyMethodNameYouWant(MyNews news) {
// Handle news
}
}
The @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
}
}
Lastly, EventBus
尊重层次结构,所以如果你有一个扩展的类MyNews
, 例如MyExtendedNews
,然后调度MyExtendedNews
事件也会传递给关心的人MyNews
事件。接口也是如此。通过这种方式,你甚至可以创建一个全局订阅者:
public class GlobalSubscriber {
// ...
@Subscribe
public void handleAllTheThings(News news) {
// Handle news
}
}