简体   繁体   English

泛型和(超级?)类型令牌可以帮助构建类型安全的新闻聚合器吗?

[英]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. SportsNewsFinancialNews等具体方法一样,提供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
  • there's no way to reference the current type in a type argument 没有办法在类型参数中引用当前类型

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库),我建议您先向下滚动并查看其他解决方案。

Vanilla Java 香草Java

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都可以保证接受您注册的类型。


Using Guava 使用番石榴

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM