6

我有这个基本News界面

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

和具体的类,如SportsNewsFinancialNews,提供具体的方法,如getStockPrice()getSport()等等。新闻旨在发送到

interface Subscriber<N extends News> {
    void onNews(N news);
}

问题是如何注册和维护订阅。我尝试的第一种方法是使用 central ,在对象和Aggregator之间保持映射,但很快这种方法就变得不可行了。这是所需的 APIClass<T>Set<Subscriber<T>>

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能完全解决这个问题吗?我将这个小演示放在网上,以帮助您更好地了解问题的真正含义。

更新

另一种方法是Aggregator使用实​​际的新闻类型参数化。这没关系,只是这是一个先有鸡还是先有蛋的问题:现在需要找到一种方法来检索聚合器。在 Java 中没有办法表达以下内容

interface News {
    static Aggregator<CurrentClass> getAggregator();
}
  • static方法不能abstract
  • 无法在类型参数中引用当前类型
4

4 回答 4

4

这就是我要做的。如果您可以使用 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 有一个 multimap 实现,它将为您执行多个值的这个键。只需谷歌“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;
}

您的private方法和字段不需要是类型安全的。无论如何它不会引起任何问题,因为 Java 的泛型是通过擦除实现的,所以这里的所有集合无论如何都只是一组对象。试图让它们类型安全只会导致讨厌的、不必要的强制转换,这与它的正确性无关。

重要的您的public方法是类型安全的。泛型的声明方式Subscriber和公共方法 on Aggregator,打破它的唯一方法是通过原始类型,就像我上面所说的那样。简而言之,只要没有不安全的强制转换或原始类型,就可以保证Subscriber每个传递给 register的类型都接受您正在注册的类型。


使用番石榴

或者,您可以查看 Guava 的EventBus. IMO,对于您正在尝试做的事情,这会更容易。

Guava 的EventBus类使用注解驱动的事件分派而不是接口驱动。这真的很简单。您将不再有Subscriber界面。相反,您的实现将如下所示:

public class MySubscriber {
    // ...

    @Subscribe
    public void anyMethodNameYouWant(MyNews news) {
        // Handle news
    }
}

@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
    }
}

最后,EventBus尊重层次结构,所以如果你有一个扩展的类MyNews,例如MyExtendedNews,那么调度MyExtendedNews事件也将传递给那些关心MyNews事件的人。接口也是如此。这样,你甚至可以创建一个全局订阅者:

public class GlobalSubscriber {
    // ...

    @Subscribe
    public void handleAllTheThings(News news) {
        // Handle news
    }
}
于 2012-10-23T21:26:07.437 回答
1

需要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
    }
}
于 2012-10-23T21:35:06.313 回答
0

subscriber.getClass()您可以获取, by的超类型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;  

这适用于简单的情况。

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

于 2012-10-23T20:09:04.170 回答
0

一种方法可以是使用TypeToken来保存N

 Type typeOfCollectionOfFoo = new TypeToken<Collection<Foo>>(){}.getType() 

使您的代码工作

将您的班级声明为

public static class Aggregator<N extends News>

将方法签名更改为

 private Set<Subscriber<N>> getSubscribersFor() {

你完成了。

于 2012-10-23T20:18:46.577 回答