簡體   English   中英

在Java中避免'instanceof'

[英]Avoiding 'instanceof' in Java

我有以下(可能是常見的)問題,現在絕對讓我困惑:

有幾個生成的事件對象擴展了抽象類Event ,我想把它們分成Session Beans,比如

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

}

但是將來可能會有兩種以上的事件類型,所以if-else將會很長並且可能無法讀取。 另外我認為在這種情況下, instanceof並不是真正的“最佳實踐”。

我可以向Event類型添加一個抽象方法,讓它們自行划分但是我必須在每個實體中注入特定的Session Bean。

是否有任何暗示可以為這個問題實現“漂亮”的解決方案?

謝謝你的幫助!

最簡單的方法是讓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);
    }
}

多態性是你的朋友。

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

然后就是

 DocumentGenerator dg = new DocumentGenerator();

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

更新

許多人提出反對意見,“你必須在編譯時知道各種事件。” 而且,是的,您顯然必須知道您在生成器部件的編譯時要解釋的事件,否則您何時可以編寫生成部件?

這些競爭示例使用Command模式,這很好,但意味着事件不僅要知道它們的表示細節,還要知道如何打印它們的表示。 這意味着每個類可能有兩種需求變化,它們的敏感性:事件所代表的變化,以及事件在打印中的表示方式的變化。

現在,考慮一下,例如,需要將其國際化。 在Command-pattern的情況下,你必須轉到n 不同的事件類型的類,並編寫新的do方法。 在多態性的情況下,更改被本地化為一個類。

當然,如果您需要進行一次國際化,您可能需要多種語言,這會促使您在Command-pattern案例中為每個類添加類似策略的內容,現在需要n個類× m種語言; 再次,在多態性情況下,您只需要一個策略和一個類。

有理由選擇任何一種方法,但聲稱多態方法是錯誤的只是不正確。

每個事件都有一個功能,比如do。 每個子類都覆蓋do,以執行(:P)適當的操作。 動態調度之后會執行其他所有操作。 你需要做的就是調用event.do()

我沒有評論權,我也不知道確切的答案。 但這只是我或一些人在這里建議使用重載(這發生在編譯時,因此只是生成編譯錯誤)來解決這個問題?

只是一個例子。 如你所見,它不會編譯。

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);
    }

利用方法解析順序有什么問題?

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

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

讓Java完成匹配正確參數類型的工作,然后正確地調度事件。

這是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());
}

在這里,我使用了最大可能的關注點分離,以便每個類和接口的責任是唯一的。 在現實生活中,項目DocumentEventImplDocumentEvent實現和DocumentEvent接口聲明通常合並到單個類DocumentEvent ,但這會引入循環依賴關系並強制在具體類之間產生一些依賴關系(我們知道,人們應該更喜歡依賴於接口)。

另外, void通常應該用類型參數替換以表示結果類型,如下所示:

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

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

這允許人們使用無狀態訪問者,這是非常好的處理。

這種技術允許(幾乎?)總是機械地消除instanceof而不是必須找出特定問題的解決方案。

您可以針對每種事件類型注冊每個處理程序類,並在事件發生時執行調度。

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注冊這些處理程序。

你可以有一個Dispatcher接口,定義如下

interface Dispatcher {
    void doDispatch(Event e);
}

使用DocEventDispatcherMailEventDispatcher等實現

然后定義一個Map<Class<? extends Event>, Dispatcher> 使用(DocEvent, new DocEventDispatcher())等條目Map<Class<? extends Event>, Dispatcher> 然后您的調度方法可以簡化為:

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()));
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM