programing tip

자바에서 'instanceof'피하기

itbloger 2020. 11. 25. 07:45
반응형

자바에서 'instanceof'피하기


나는 다음과 같은 (아마도 일반적인) 문제가 있으며 현재로서는 절대적으로 당황합니다.

추상 클래스를 확장하는 몇 가지 생성 된 이벤트 객체가 있으며 Event이를 세션 빈으로 나누고 싶습니다.

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

}

그러나 앞으로 두 가지 이상의 이벤트 유형이있을 수 있으므로 if-else는 길고 읽을 수 없습니다. 또한 instanceof이 경우에는 실제로 "모범 사례"가 아니라고 생각 합니다.

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 개의 클래스 로 이동하여 새로운 do 메서드를 작성 해야 합니다. 다형성의 경우 변경 사항은 하나의 클래스로 지역화됩니다.

당연히 한 번 국제화해야하는 경우 여러 언어가 필요할 수 있습니다. 따라서 명령 패턴의 경우 각 클래스전략과 같은 것을 추가 해야하므로 이제 n 개의 클래스 × m 개의 언어가 필요 합니다. 다시 말하지만 다형성의 경우 하나의 전략과 하나의 클래스 만 있으면됩니다.

두 가지 접근법 중 하나를 선택해야하는 이유가 있지만 다형성 접근법이 잘못되었다고 주장하는 것은 잘못된 것 입니다.


각 이벤트에는 기능이 있습니다. 각 하위 클래스는 적절한 작업을 수행 (: 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);
    }
}

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

여기서는 각 클래스와 인터페이스의 책임이 단 하나가되도록 최대한의 우려 사항 분리를 사용했습니다. 실제 프로젝트 DocumentEventImpl에서 DocumentEvent구현 및 DocumentEvent인터페이스 선언은 일반적으로 단일 클래스로 병합 DocumentEvent되지만 순환 종속성을 도입하고 구체적인 클래스간에 일부 종속성을 강제합니다 (알다시피 인터페이스에 의존하는 것을 선호해야 함).

또한 void일반적으로 다음과 같이 결과 유형을 나타내는 유형 매개 변수로 대체해야합니다.

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

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

이를 통해 무국적 방문자를 사용할 수 있으며 처리하기에 매우 좋습니다.

This technique allows to (almost?) always eliminate instanceof mechanically rather than having to figure out a problem-specific solution.


You could register each of your handler classes against each event type, and perform dispatch when event happens like this.

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

And then get your specific handlers to implement the interface, and register those handlers with the EventRegister.


You could have a Dispatcher interface, defined like

interface Dispatcher {
    void doDispatch(Event e);
}

with implementations like DocEventDispatcher, MailEventDispatcher, etc.

Then define a Map<Class<? extends Event>, Dispatcher>, with entries like (DocEvent, new DocEventDispatcher()). Then your dispatch method could be reduced to:

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

Here's a unit test:

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

참고URL : https://stackoverflow.com/questions/6157835/avoiding-instanceof-in-java

반응형