6

私の質問は、可能な限り拡張可能な方法で、さまざまなメッセージに対してさまざまな動作を実装することです。私は訪問者のパターンを認識しており、二重ディスパッチを認識していますが、私を満足させる解決策を見つけられないようです(少なくともJavaの制限内ではありません)。

私の状況は次のとおりです。

私はメッセージの階層を持っています:

メッセージ階層

およびルーター インターフェイスの階層。それぞれが独自のメッセージ タイプのルート メソッドを定義します。

ルーター インターフェイス階層

私はこれに似たものを実装したいと思います:

実装

特定のメッセージをルーティングする機能を追加および削除したり、特定のメッセージのルーティング戦略を簡単に変更したりできます。

問題は、やりたくないメッセージをスイッチキャストしないと、インターフェースのそれぞれの機能を選択できないことです。

CompositeRouter comp = new AllRouter(...//new Router instances);
MessageBase msg = new DerivedMessage();
msg.process(comp);

オーバーロードを選択するJavaにつながります<runtime message-type>.process(Router)

これは、実行時にそれぞれのルーター オブジェクトに対して呼び出されます。そのため、コンパイル時に process() への正しい呼び出しを選択できないようです。その逆もできないので、comp.route(msg)

に解決され<dynamic router-type>.route(MessageBase)ます。

CompositeRouter から適切なメソッドを選択するビジターを作成することもできますが、そのためには、事前にすべての MessageTypes に対して定義されたそれぞれの route-Methods を使用してビジター インターフェイスを定義する必要があります。新しい DerivedMessage を追加するたびに、訪問者を書き換える必要があります。

Message と Router の両方が拡張可能になるようにこれを実装する方法はありますか、それとも現在の Java 機能では絶望的ですか?

編集1:

Router私が言及するのを忘れていたのは、 -hierarchyとほとんど同じである他の 4 つまたは 5 つの状況があるということです。そのため、ランタイム コストが心配なので、メソッド ルックアップのリフレクションは避けたいと思います。

コメントへの対応:

  1. @bot の提案に関する @aruisdante の仮定は正しいです。route(MessageBase) をオーバーライドすると、MessageBase のランタイム タイプが失われるため、オーバーライドできません。

  2. @aruisdanteと@geceo:私はそれができることを知っています-これは「スイッチキャスティング」で意味したことです(MessageBaseにはMessageTypeフィールドがあります)-しかし、コードには11の実際のメッセージクラスと〜6の場所が必要です、したがって、それはメンテナンスの面だけでなく、実装の面でも大きな苦痛になるでしょう。

4

3 に答える 3

4

これは、過去にこのような問題を通常どのように解決したかです。

まず、あなたのインターフェースでは、Composite を除いてRouterほとんどの実装で 1 つのメッセージ タイプのみを処理するように意図しているように思われるため、インターフェースの定義を次のようなものに変更します。Router

interface Router<T extends MessageBase> {
    void route(T message);

}

Routerこれにより、特定の実装を処理するさまざまな にインターフェイスを提供する必要がなくなります。派生Routerクラスは次のようになります。

class OtherDerivedRouter implements Router<OtherDerivedMessage> {

    @Override
    void route(OtherDerivedMessage message) { //... };

}

で何が起こるのCompositeRouterでしょうか?さて、私たちは次のようなことをします:

class CompositeRouter implements Router<MessageBase> {

    protected static class RouterAdaptor< T extends MessageBase> implements Router<MessageBase> {

         private Router<T> router;
         private Class<T>  klass;

         RouterAdaptor(Router<T> router, Class<T> klass) {
             this.router = router;
             this.klass  = klass;
         }

         @Override
         public void route(MessageBase message) {
            try {
                router.route(klass.cast(message));
            } (catch ClassCastException e) {
                // Do whatever, something's gone wrong if this happens
            }
         }
     }

     private Map<Class<?>, RouterAdaptor<?>> routerMap;

     @Override
     public void route(MessageBase message) {
         RouterAdaptor<?> adaptor = routerMap.get(message.getClass());
         if (adaptor != null) {
             adaptor.route(message)
         } else {
           // do your default routing case here
         }
     }

     public <T extends MessageBase> void registerRouter(Router<T> router, Class<T> klass) {
         // Right now don't check for overwrite of existing registration, could do so here
         routerMap.put(klass, new RouterAdaptor<T>(router, kass));
     }

     CompositeRouter(/*...*/) {
         //initialize routerMap with Map type of choice, etc
     }

}

は、保持する実装がRouterAdaptor期待する正しいメッセージ タイプをディスパッチするという大変な作業を行います。RouterそしてCompositeRouter、これらのアダプタのレジストリをメッセージ タイプに格納するだけで済みます。

このアプローチの最大の欠点は、Type ErasureRouterのおかげで、複数のメッセージ タイプを直接処理する実装を作成する方法がないことです。Java の見通しから、 at runtimeRouter<MessageBase>は と同じであるため、C++ テンプレートの場合とは異なり、Router<OtherDerivedMessage>のようなものを持つことは違法です。これは、単に型を から直接推測できるのではなく、明示的なオブジェクトSuperRouter implements Router<MessageBase>, Router<OtherDerivedMessage>を渡す必要がある理由でもあります。Class<T>Router<T>

于 2015-03-06T17:15:30.963 に答える
2

Router実装とそれに対応するメッセージタイプの「レジストリ」を持つことができます。

class CompositeRouter implements Router {

   private Map<Class,Router> registry = new HashMap<>()

   void registerRouter(Class<? extends MessageBase> messageClass, Router router) {
     register.put(messageClass, router);
   }

   @Override
   void process(MessageBase message) {
     // here you can implement more sophisticated logic
     // to find most appropriate Router for given message according 
     // type hierarchy
     Router router = registry.get(message.getClass());
     router.process(message);
   } 
} 
于 2015-03-06T16:27:13.743 に答える
1

Scala (別の JVM 言語) では、これは型クラスの使用例のように思えます。

http://danielwestheide.com/blog/2013/02/06/the-neophytes-guide-to-scala-part-12-type-classes.html

これはJavaでは不可能ですが、「Java型クラス」をグーグルで検索すると、実験的なライブラリがいくつかあります。

于 2015-03-06T16:03:55.910 に答える