在Java中避免使用\'instanceof\'

问题描述

| 我有以下(也许很常见)问题,此刻绝对使我感到困惑: 有几个生成的事件对象扩展了抽象类
Event
,我想将它们划分为Session Bean,例如
public void divideEvent(Event event) {
    if (event instanceof DocumentEvent) {
        documentGenerator.gerenateDocument(event);
    } else if (event instanceof MailEvent) {
        deliveryManager.deliverMail(event);
        ...
    }
    ...

}
但是将来可能会有两种以上的事件类型,因此if-else将会很长,甚至可能无法读取。另外,我认为在这种情况下,
instanceof
并不是真正的“最佳实践”。 我可以将一个抽象方法添加到“ 0”类型,并让它们自己划分,但是然后我必须在每个实体中注入特定的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);
更新资料 许多人提出了反对意见,即“您必须在编译时知道事件的种类。”而且,是的,您显然必须知道在生成器部分的编译时要解释的事件是什么,何时?否则您将能够编写生成部分? 这些竞争示例使用命令模式,这很好,但是这意味着事件不仅要知道其表示的细节,而且还必须知道如何打印其表示的细节。这意味着每个类可能对其敏感的需求有两种更改:事件表示方式的更改以及事件在打印中的表示方式的更改。 现在,考虑例如需要对此进行国际化。在命令模式的情况下,您必须转到n个类以获取n种不同的Event类型,并编写新的do方法。在多态的情况下,更改将局限于一类。 自然,如果您需要一次国际化,则可能需要多种语言,这会导致您在Command-pattern情况下向每个类添加诸如Strategy之类的东西,现在需要n种×m种语言;同样,在多态情况下,您只需要一个策略和一个类。 选择其中一种方法是有原因的,但是断言多态方法是错误的就是错误的。     ,        每个事件都有一个功能,比如说do。 每个子类都重写do,以执行(:P)适当的操作。 动态调度之后会执行其他所有操作。 您需要做的就是调用event.do()     ,        我没有评论权,我不知道确切的答案。但是只是我还是这里的一些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);
    }
    ,        利用方法解析顺序有什么问题?
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);
    }
}
这里我们定义了“ 10”,现在提供了一种调度机制:
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());
}
在这里,我使用了最大程度的关注点分离,以使每个类和接口的责任是一个,而且仅仅是一个。在现实生活中,项目
DocumentEventImpl
DocumentEvent
实现和
DocumentEvent
接口声明通常合并为一个类
DocumentEvent
,但这会引入循环依赖关系,并迫使具体类之间存在某些依赖关系(众所周知,应该更依赖于接口)。 此外,通常应将“ 16”替换为类型参数以表示结果类型,如下所示:
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);
}
带有
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()));
    }
}
    

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...