仿制药和(超?)可以输入令牌有助于建立一个类型安全的新闻聚合?(Can generics and (

2019-08-01 19:16发布

我有这个基本的News界面

interface News {
    String getHeader();
    String getText();
}

和具体的类等SportsNewsFinancialNews提供像具体方法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
  • 有没有办法来引用一个类型参数当前类型

Answer 1:

这就是我会做。 如果你可以用番石榴(谷歌的图书馆由谷歌书面和使用),我建议向下滚动,并在其他的解决办法首先查找。

香草的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
    }
}


Answer 2:

需要发送的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
    }
}


Answer 3:

你可以得到超类型的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;  

这适用于简单的情况。

对于更复杂的情况下,我们需要的类型更复杂的算法。



Answer 4:

一种方法可以是使用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?