[英]Can generics and (super?) type tokens help to build a type-safe news aggregator?
I have this basic News
interface 我有这个基本的
News
界面
interface News {
String getHeader();
String getText();
}
and concrete classes like SportsNews
and FinancialNews
to provide specific methods like getStockPrice()
, getSport()
and so on. 和
SportsNews
和FinancialNews
等具体方法一样,提供getStockPrice()
, getSport()
等特定方法。 News are intended to be dispatched to a 新闻旨在发送给
interface Subscriber<N extends News> {
void onNews(N news);
}
The problem is how to register and maintain subscriptions. 问题是如何注册和维护订阅。 The first approach I tried was using a central
Aggregator
, keeping a map between Class<T>
objects and Set<Subscriber<T>>
, but soon this approach revealed unviable. 我尝试的第一种方法是使用中央
Aggregator
,在Class<T>
对象和Set<Subscriber<T>>
之间保持映射,但很快这种方法显示不可行。 Here is the desired API 这是所需的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
}
}
Is there any alternative to be type safe? 有什么替代品可以安全吗? Can Java solve this problem at all?
Java可以解决这个问题吗? I put this little demo online to help you better understand what the problem really is.
我把这个小小的演示放在网上,以帮助你更好地理解问题的真正含义。
UPDATE UPDATE
An alternative would be to make Aggregator
itself parameterized with the actual news type. 另一种方法是使
Aggregator
本身使用实际新闻类型进行参数化。 This would be ok, except that it's a chicken and egg problem: now one needs to find a way to retrieve the aggregator. 这没关系,除了它是一个鸡和蛋的问题:现在需要找到一种方法来检索聚合器。 In Java there's no way to express the following
在Java中,没有办法表达以下内容
interface News {
static Aggregator<CurrentClass> getAggregator();
}
static
method can't be abstract
static
方法不能是abstract
Here's what I would do. 这就是我要做的。 If you can use Guava (a Google library written and used by Google), I recommend scrolling down and looking at the other solution first.
如果您可以使用Guava(由Google编写和使用的Google库),我建议您先向下滚动并查看其他解决方案。
First, start by adding a method to get the class from your subscribers: 首先,首先添加一个方法从订阅者处获取类:
public interface Subscriber<N extends News> {
void onNews(N news);
Class<N> getSupportedNewsType();
}
Then when implementing: 然后在实施时:
public class MySubscriber implements Subscriber<MyNews> {
// ...
public Class<MyNews> getSupportedNewsType() {
return MyNews.class;
}
}
In your aggregator, include a map where the keys and values aren't typed: 在聚合器中,包括未键入键和值的映射:
private Map<Class<?>, Set<Subscriber<?>> subscribersByClass = ... ;
Also note that Guava has a multimap implementation that will do this key to multiple values stuff for you. 另请注意,Guava有一个multimap实现,可以为您提供多个值的关键。 Just Google "Guava Multimap" and you'll find it.
只需Google“Guava Multimap”即可找到它。
To register a subscriber: 注册用户:
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);
}
And to dispatch: 并派遣:
@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);
}
}
Notice the cast here. 注意这里的演员。 This will be safe because of the nature of the generics between the
register
method and the Subscriber
interface, provided no one does something ridiculously wrong, like raw-typing such as implements Subscriber
(no generic argument). 这是安全的,因为
register
方法和Subscriber
接口之间的泛型的性质,只要没有人做出一些荒谬的错误,例如raw-typing,如implements Subscriber
(没有泛型参数)。 The SuppressWarnings
annotation suppresses warnings about this cast from the compiler. SuppressWarnings
注释会抑制编译器对此演员的警告。
And your private method to retrieve subscribers: 以及检索订阅者的私有方法:
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
methods and fields do not need to be type safe. 您的
private
方法和字段不需要是类型安全的。 It won't cause any problems anyway since Java's generics are implemented via erasure, so all of the sets here will be just a set of objects anyway. 它不会引起任何问题,因为Java的泛型是通过擦除来实现的,所以这里的所有集合无论如何都只是一组对象。 Trying to make them type safe will only lead to nasty, unnecessary casts that have no bearing on its correctness.
试图使它们类型安全只会导致令人讨厌的,不必要的演员阵容,这与其正确性无关。
What does matter is that your public
methods are type safe. 什么事情确实是,你的
public
方法是类型安全的。 The way the generics are declared in Subscriber
and the public methods on Aggregator
, the only way to break it is via raw types, like I stated above. 在
Subscriber
中声明泛型的方式和Aggregator
上的公共方法,打破它的唯一方法是通过原始类型,就像我上面所说的那样。 In short, every Subscriber
passed to register is guaranteed to accept the types that you're registering it for as long as there's no unsafe casts or raw typing. 简而言之,只要没有不安全的演员表或原始打字,每个通过注册的
Subscriber
都可以保证接受您注册的类型。
Alternatively, you can take a look at Guava's EventBus
. 或者,您可以看看Guava的
EventBus
。 This would be easier, IMO, for what you're trying to do. 对于你想要做的事情,IMO会更容易。
Guava's EventBus
class uses annotation-driven event dispatching instead of interface-driven. Guava的
EventBus
类使用注释驱动的事件调度而不是接口驱动。 It's really simple. 这很简单。 You won't have a
Subscriber
interface anymore. 您将不再拥有
Subscriber
界面。 Instead, your implementation will look like this: 相反,您的实现将如下所示:
public class MySubscriber {
// ...
@Subscribe
public void anyMethodNameYouWant(MyNews news) {
// Handle news
}
}
The @Subscribe
annotation signals to Guava's EventBus
that it should remember that method later for dispatching. @Subscribe
注释向Guava的EventBus
发出信号,它应该记住该方法以便进行调度。 Then to register it and dispatch events, use an EventBus
isntance: 然后注册它并发送事件,使用
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);
}
}
This will automatically find the methods that accept the news
object and do the dispatching for you. 这将自动找到接受
news
对象的方法并为您执行调度。 You can even subscribe more than once in the same class: 您甚至可以在同一个班级中多次订阅:
public class MySubscriber {
// ...
@Subscribe
public void anyMethodNameYouWant(MyNews news) {
// Handle news
}
@Subscribe
public void anEntirelyDifferentMethod(MyNews news) {
// Handle news
}
}
Or for multiple types within the same subscriber: 或者对于同一订户中的多种类型:
public class MySubscriber {
// ...
@Subscribe
public void handleNews(MyNews news) {
// Handle news
}
@Subscribe
public void handleNews(YourNews news) {
// Handle news
}
}
Lastly, EventBus
respects hierarchical structures, so if you have a class that extends MyNews
, such as MyExtendedNews
, then dispatching MyExtendedNews
events will also be passed to those that care about MyNews
events. 最后,
EventBus
尊重的层次结构,所以如果你有一个扩展一个类MyNews
,如MyExtendedNews
,然后调度MyExtendedNews
事件也将被传递给那些关心MyNews
事件。 Same goes for interfaces. 接口也是如此。 In this way, you can even create a global subscriber:
通过这种方式,您甚至可以创建全局订阅者:
public class GlobalSubscriber {
// ...
@Subscribe
public void handleAllTheThings(News news) {
// Handle news
}
}
You will need to send the class
parameter to dispatch
. 您将需要发送的
class
参数来dispatch
。 The following compiles for me, not sure if that meets your needs: 以下内容为我编译,不确定是否符合您的需求:
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
}
}
You can get the super types of subscriber.getClass()
, by Class.getGenericSuperclass/getGenericInterfaces()
, then inspect them to extract which N
really is, by ParameterizedType.getActualTypeArguments()
您可以通过
Class.getGenericSuperclass/getGenericInterfaces()
获取超类型的subscriber.getClass()
,然后通过ParameterizedType.getActualTypeArguments()
检查它们以提取哪个N
确实是
For example 例如
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;
That works for simple cases. 这适用于简单的情况。
For more complicated case, we'll need more complicated algorithms on types. 对于更复杂的情况,我们需要更复杂的类型算法。
A way could be use a TypeToken to hold N
一种方法可以使用TypeToken来保存
N
Type typeOfCollectionOfFoo = new TypeToken<Collection<Foo>>(){}.getType()
To make your code work 使代码工作
Declare your class as 将你的班级宣布为
public static class Aggregator<N extends News>
Change method signature to 将方法签名更改为
private Set<Subscriber<N>> getSubscribersFor() {
And you are done. 你完成了。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.