我有这个基本的News
界面
interface News {
String getHeader();
String getText();
}
和具体的类等SportsNews
和FinancialNews
提供像具体方法getStockPrice()
getSport()
等等。 新闻意图被分派到
interface Subscriber<N extends News> {
void onNews(N news);
}
问题是如何注册和维护订阅。 我尝试的第一个方法是使用中央Aggregator
,保持之间的映射Class<T>
对象和Set<Subscriber<T>>
,但很快这种方法揭示不可行。 以下是所希望的API
public class Aggregator {
public <N extends News> void subscribe(Subscriber<N> subscriber) {
// TODO somehow (super type token) extract N and
// add the item to the set retrieved by getSubscribersFor()
}
public <N extends News> void dispatch(N news) {
for (Subscriber<N> subscriber: getSubscribersFor(news.getClass())) {
subscriber.onNews(news);
}
}
private <N extends News> Set<Subscriber<N>> getSubscribersFor(Class<N> k) {
// TODO retrieve the Set for the specified key from the Map
}
}
是否有任何替代类型安全的? Java可以解决这个问题呢? 我把这个小演示网上 ,以帮助您更好地了解真正的问题是什么。
UPDATE
另一种方法是,使Aggregator
本身参数与实际的新闻类型。 这将是确定的,但它是一个鸡和蛋的问题:现在人们需要找到一种方法来检索聚合。 在Java中没有办法表达以下
interface News {
static Aggregator<CurrentClass> getAggregator();
}
-
static
方法不能是abstract
- 有没有办法来引用一个类型参数当前类型
这就是我会做。 如果你可以用番石榴(谷歌的图书馆由谷歌书面和使用),我建议向下滚动,并在其他的解决办法首先查找。
香草的Java
首先,通过添加一个方法来从用户获取类开始:
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 = ... ;
还要注意的是番石榴具有多重映射实现,将做到这一点的关键多个值的东西给你。 只是谷歌“番石榴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
,并在公共方法Aggregator
,打破它的唯一方法是通过原始类型,如我上面所述。 总之,每一个Subscriber
通过注册,是保证接受你注册它,只要没有不安全的类型转换或原始类型的类型。
使用番石榴
或者,你可以看看番石榴的EventBus
。 这会更容易些,国际海事组织,因为你正在试图做什么。
番石榴的EventBus
类使用注解驱动的事件分派,而不是接口驱动。 这真的很简单。 你不会有一个Subscriber
界面了。 相反,你的实现将是这样的:
public class MySubscriber {
// ...
@Subscribe
public void anyMethodNameYouWant(MyNews news) {
// Handle news
}
}
该@Subscribe
注释信号番石榴的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
对象,并为你做的调度。 你甚至可以订阅不止一次在同一类:
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
}
}
您将需要发送的class
参数来dispatch
。 对我来说,下面的编译,不知道是否能够满足您的需求:
import java.util.Set;
interface News {
String getHeader();
String getText();
}
interface SportsNews extends News {}
interface Subscriber<N extends News> {
void onNews(N news);
}
class Aggregator {
public <N extends News> void subscribe(Subscriber<N> subscriber, Class<N> clazz) {
// TODO somehow (super type token) extract N and
// add the item to the set retrieved by getSubscribersFor()
}
public <N extends News> void dispatch(N item, Class<N> k) {
Set<Subscriber<N>> l = getSubscribersFor(k);
for (Subscriber<N> s : l) {
s.onNews(item);
}
}
private <N extends News> Set<Subscriber<N>> getSubscribersFor(Class<N> k) {
return null;
// TODO retrieve the Set for the specified key from the Map
}
}
你可以得到超类型的subscriber.getClass()
由Class.getGenericSuperclass/getGenericInterfaces()
然后检查他们以提取N
真的是,通过ParameterizedType.getActualTypeArguments()
例如
public class SportsLover implements Subscriber<SportsNews>
{
void onNews(SportsNews news){ ... }
}
if subscriber is an instance of SportsLover
Class clazz = subscriber.getClass(); // SportsLover.class
// the super type: Subscriber<SportsNews>
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<Collection<Foo>>(){}.getType()
为了使你的代码工作
声明你的类作为
public static class Aggregator<N extends News>
更改方法签名
private Set<Subscriber<N>> getSubscribersFor() {
和你做。
文章来源: Can generics and (super?) type tokens help to build a type-safe news aggregator?