6

私はこの基本的なNewsインターフェースを持っています

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

や などの具象クラスは、 などのSportsNews特定FinancialNewsのメソッドを提供します。ニュースは、getStockPrice()getSport()

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

問題は、サブスクリプションを登録して維持する方法です。私が試した最初のアプローチは、中央の を使用して、オブジェクトとAggregatorの間のマップを維持することでしたが、すぐにこのアプローチは実行できないことがわかりました。ここに目的の API がありますClass<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には、このキーを複数の値に変換するマルチマップ実装があることに注意してください。「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);
    }
}

ここでキャストに注目。これは、 (ジェネリック引数なし)などの raw-typing のようなばかげた間違いを誰も行わない限り、registerメソッドとインターフェイスの間のジェネリックの性質により安全です。注釈は、コンパイラからのこのキャストに関する警告を抑制します。Subscriberimplements SubscriberSuppressWarnings

そして、サブスクライバーを取得するためのプライベート メソッド:

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. これは、あなたがやろうとしていることに対して、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

パラメータをに送信する必要があります。以下は私のためにコンパイルしますが、それがあなたのニーズを満たしているかどうかはわかりません:classdispatch

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()あるものを抽出できます。NParameterizedType.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 に答える