web-dev-qa-db-ja.com

Javaでの 'instanceof'の回避

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

抽象クラス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を挿入する必要があります。

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

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

63
fnst

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

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);
    }
}
54
Peter Lawrey

多態性はあなたの友達です。

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

じゃあ

 DocumentGenerator dg = new DocumentGenerator();

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

更新

多くの人々が、「コンパイル時にイベントの種類を知っている必要がある」という異議を唱えています。そして、はい、ジェネレーター部分のコンパイル時にどのイベントを解釈しているのかを明確に知る必要があります。他にいつ生成部分を作成できるのでしょうか?

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

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

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

どちらのアプローチを選択するかには理由がありますが、ポリモーフィズムアプローチを主張するのはwrongは正しくありません。

11
Charlie Martin

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

8

私にはコメント権限がなく、正確な答えがわかりません。しかし、私またはここのいくつかのpplがこの問題を解決するためにオーバーロード(コンパイル時に発生し、したがってコンパイルエラーを生成するだけ)の使用を提案していますか?

ほんの一例です。ご覧のとおり、コンパイルされません。

package com.stackoverflow;

public class Test {
    static abstract class Event {}
    static class MailEvent extends Event {}
    static class DocEvent extends Event {}

    static class Dispatcher {
        void dispatchEvent(DocEvent e) {
            System.out.println("A");
        }

        void dispatchEvent(MailEvent e) {
            System.out.println("B");
        }
    }

    public static void main(String[] args) {
        Dispatcher d = new Dispatcher();
        Event e = new DocEvent();

        d.dispatchEvent(e);
    }
4
bigoldbrute

メソッド解決順序を活用することの何が問題になっていますか?

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

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

Javaが正しい引数の型と一致するように作業を行い、イベントを適切にディスパッチするようにします。

3

これは、タグ付きユニオンとも呼ばれる Sum types の典型的な使用例です。残念ながら、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つだけになるようにしました。実際のプロジェクトでは、DocumentEventImplDocumentEvent実装、およびDocumentEventインターフェース宣言は通常、単一のクラスDocumentEventにマージされますが、これにより循環依存関係が発生し、いくつかの依存関係が強制されます具象クラス間(そして私たちが知っているように、インターフェースに依存することを好むはずです)。

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

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

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

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

この手法により、問題固有の解決策を理解する必要がなく、常にinstanceofを機械的に常に排除できます。

2
Rotsor

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

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に登録します。

2
Yohan Liyanage

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

interface Dispatcher {
    void doDispatch(Event e);
}

DocEventDispatcherMailEventDispatcherなどの実装.

次に、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()));
    }
}
1
Ray