64

私は次の(おそらく一般的な)問題を抱えており、現時点では完全に困惑しています:

抽象クラスを拡張する生成されたイベント オブジェクトがいくつかあり、Eventそれらをセッション Bean に分割したいと考えています。

public void divideEvent(Event event) {
    if (event instanceof DocumentEvent) {
        documentGenerator.gerenateDocument(event);
    } else if (event instanceof MailEvent) {
        deliveryManager.deliverMail(event);
        ...
    }
    ...

}

しかし、将来的には 2 つ以上のイベント タイプが存在する可能性があるため、if-else は長くなり、判読不能になる可能性があります。instanceofさらに、この場合、実際には「ベストプラクティス」ではないと思います。

型に抽象メソッドを追加して、Eventそれ自体を分割することもできますが、各エンティティ内に特定のセッション Bean を注入する必要があります。

この問題の「きれいな」解決策を達成するためのヒントはありますか?

助けてくれてありがとう!

4

8 に答える 8

54

最も簡単な方法は、Event に呼び出し可能なメソッドを提供させて、Event が何をすべきかを知るようにすることです。

interface Event {
    public void onEvent(Context context);
}

class DocumentEvent implements Event {
    public void onEvent(Context context) {
         context.getDocumentGenerator().gerenateDocument(this);
    }
}

class MailEvent implements Event {
    public void onEvent(Context context) {
         context.getDeliveryManager().deliverMail(event);
    }
}


class Context {
    public void divideEvent(Event event) {
        event.onEvent(this);
    }
}
于 2011-05-27T21:28:46.013 に答える
11

ポリモーフィズムはあなたの友達です。

class DocumentGenerator {
   public void generate(DocumentEvent ev){}
   public void generate(MainEvent ev){}
   //... and so on
}

それからちょうど

 DocumentGenerator dg = new DocumentGenerator();

 // ....
 dg.generate(event);

アップデート

多くの人が、「コンパイル時にイベントの種類を知らなければならない」という異議を唱えました。そして、そうです、ジェネレーター部分のコンパイル時にどのイベントを解釈しているのかを明確に知っておく必要があります。

これらの競合する例では、コマンド パターンを使用していますが、これは問題ありませんが、イベントは、その表現だけでなく、どのように表現を印刷するかについての詳細を知る必要があることを意味します。つまり、各クラスには 2 種類の要件の変更があり、イベントが表す内容の変更と、印刷物でのイベントの表示方法の変更の 2 種類があります。

たとえば、これを国際化する必要があるとします。コマンド パターンの場合、n 個の異なるイベント タイプに対して n 個のクラスに移動し、新しい do メソッドを作成する必要あります。ポリモーフィズムの場合、変更は 1 つのクラスにローカライズされます。

当然、一度国際化する必要がある場合は、多くの言語が必要になる可能性があります。これにより、コマンドパターンの場合、各クラスに戦略のようなものを追加する必要が生じ、 nクラス × m言語が必要になります。繰り返しになりますが、ポリモーフィズムの場合は、1 つの戦略と 1 つのクラスのみが必要です。

いずれかのアプローチを選択する理由はありますが、ポリモーフィズムのアプローチが間違っていると主張するのは正しくありません。

于 2011-05-27T21:31:06.940 に答える
8

各イベントには機能があります。各サブクラスは do をオーバーライドして、適切なアクションを実行します (:P)。動的ディスパッチは、その後すべてを行います。あなたがする必要があるのは、 event.do() を呼び出すことだけです

于 2011-05-27T21:26:01.550 に答える
3

メソッド解決順序を悪用する際の問題は何ですか?

public void dispatchEvent(DocumentEvent e) {
    documentGenerator.gerenateDocument(event);
}

public void dispatchEvent(MailEvent e) {
    deliveryManager.deliverMail(event);
}

Java に正しい引数の型を一致させる作業をさせてから、イベントを適切にディスパッチします。

于 2011-05-27T21:28:14.150 に答える
2

これは、タグ付き共用体とも呼ばれるSum 型の典型的な使用例です。残念ながら、Java はこれらを直接サポートしていないため、ビジター パターンのバリエーションを使用して実装する必要があります。

interface DocumentEvent {
    // stuff specific to document event
}

interface MailEvent {
    // stuff specific to mail event
}

interface EventVisitor {
    void visitDocumentEvent(DocumentEvent event);
    void visitMailEvent(MailEvent event);
}

class EventDivider implements EventVisitor {
    @Override
    void visitDocumentEvent(DocumentEvent event) {
        documentGenerator.gerenateDocument(event);
    } 

    @Override
    void visitMailEvent(MailEvent event) {
        deliveryManager.deliverMail(event);
    }
}

ここでEventDivider、ディスパッチ メカニズムを提供するためにを定義しました。

interface Event {
    void accept(EventVisitor visitor);
}

class DocumentEventImpl implements Event {
    @Override
    void accept(EventVisitor visitor) {
        visitor.visitDocumentEvent(new DocumentEvent(){
            // concrete document event stuff
        });
    }
}

class MailEventImpl implements Event { ... }

public void divideEvent(Event event) {
    event.accept(new EventDivider());
}

ここでは、各クラスとインターフェイスの責任が 1 つだけになるように、懸念事項を可能な限り分離しました。実際のプロジェクトDocumentEventImplでは、DocumentEvent実装とDocumentEventインターフェースの宣言は通常、単一のクラスDocumentEventにマージされますが、これにより循環依存が導入され、具体的なクラス間にいくつかの依存が強制されます (そして、私たちが知っているように、インターフェースに依存することを好むはずです)。

さらに、void通常、次のように、結果の型を表す型パラメーターに置き換える必要があります。

interface EventVisitor<R> {
    R visitDocumentEvent(DocumentEvent event);
    ...
}

interface Event {
    <R> R accept(EventVisitor<R> visitor);
}

これにより、ステートレス ビジターを使用できるようになり、非常に扱いやすくなります。

この手法によりinstanceof、問題固有の解決策を見つけ出す必要はなく、(ほとんど?) 常に機械的に排除できます。

于 2011-05-28T01:59:19.590 に答える
2

各イベント タイプに対して各ハンドラ クラスを登録し、このようにイベントが発生したときにディスパッチを実行できます。

class EventRegister {

   private Map<Event, List<EventListener>> listerMap;


   public void addListener(Event event, EventListener listener) {
           // ... add it to the map (that is, for that event, get the list and add this listener to it
   }

   public void dispatch(Event event) {
           List<EventListener> listeners = map.get(event);
           if (listeners == null || listeners.size() == 0) return;

           for (EventListener l : listeners) {
                    l.onEvent(event);  // better to put in a try-catch
           }
   }
}

interface EventListener {
    void onEvent(Event e);
}

次に、特定のハンドラーを取得してインターフェイスを実装し、それらのハンドラーを EventRegister に登録します。

于 2011-05-28T07:18:59.727 に答える
1

あなたはDispatcher次のように定義されたインターフェースを持つことができます

interface Dispatcher {
    void doDispatch(Event e);
}

、などDocEventDispatcherの実装でMailEventDispatcher

次に、のMap<Class<? extends Event>, Dispatcher>ようなエントリで、を定義します(DocEvent, new DocEventDispatcher())。次に、ディスパッチ方法を次のように減らすことができます。

public void divideEvent(Event event) {
    dispatcherMap.get(event.getClass()).doDispatch(event);
}

単体テストは次のとおりです。

public class EventDispatcher {
    interface Dispatcher<T extends Event> {
        void doDispatch(T e);
    }

    static class DocEventDispatcher implements Dispatcher<DocEvent> {
        @Override
        public void doDispatch(DocEvent e) {

        }
    }

    static class MailEventDispatcher implements Dispatcher<MailEvent> {
        @Override
        public void doDispatch(MailEvent e) {

        }
    }


    interface Event {

    }

    static class DocEvent implements Event {

    }

    static class MailEvent implements Event {

    }

    @Test
    public void testDispatcherMap() {
        Map<Class<? extends Event>, Dispatcher<? extends Event>> map = new HashMap<Class<? extends Event>, Dispatcher<? extends Event>>();
        map.put(DocEvent.class, new DocEventDispatcher());
        map.put(MailEvent.class, new MailEventDispatcher());

        assertNotNull(map.get(new MailEvent().getClass()));
    }
}
于 2011-05-28T02:17:42.663 に答える