[java] Java에서 instanceof 피하기

일련의 “instanceof”작업을 갖는 것은 “코드 냄새”로 간주됩니다. 표준 대답은 “다형성 사용”입니다. 이 경우 어떻게해야합니까?

기본 클래스에는 여러 하위 클래스가 있습니다. 그들 중 누구도 내 통제하에 있지 않습니다. 유사한 상황은 Java 클래스 Integer, Double, BigDecimal 등입니다.

if (obj instanceof Integer) {NumberStuff.handle((Integer)obj);}
else if (obj instanceof BigDecimal) {BigDecimalStuff.handle((BigDecimal)obj);}
else if (obj instanceof Double) {DoubleStuff.handle((Double)obj);}

나는 NumberStuff 등을 제어 할 수 있습니다.

몇 줄로 할 수있는 코드 줄을 많이 사용하고 싶지 않습니다. (때로는 Integer.class를 IntegerStuff의 인스턴스로, BigDecimal.class를 BigDecimalStuff의 인스턴스로 매핑하는 HashMap을 만듭니다.하지만 오늘은 더 간단한 것을 원합니다.)

다음과 같이 간단한 것을 원합니다.

public static handle(Integer num) { ... }
public static handle(BigDecimal num) { ... }

그러나 Java는 그렇게 작동하지 않습니다.

서식을 지정할 때 정적 메서드를 사용하고 싶습니다. 내가 서식을 지정하는 것은 복합적입니다. Thing1은 Thing2 배열을 포함 할 수 있고 Thing2는 Thing1 배열을 포함 할 수 있습니다. 다음과 같은 포맷터를 구현할 때 문제가 발생했습니다.

class Thing1Formatter {
  private static Thing2Formatter thing2Formatter = new Thing2Formatter();
  public format(Thing thing) {
      thing2Formatter.format(thing.innerThing2);
  }
}
class Thing2Formatter {
  private static Thing1Formatter thing1Formatter = new Thing1Formatter();
  public format(Thing2 thing) {
      thing1Formatter.format(thing.innerThing1);
  }
}

예, 저는 HashMap을 알고 있으며 더 많은 코드로도 해결할 수 있습니다. 그러나 “instanceof”는 비교해 보면 매우 읽기 쉽고 유지 관리가 가능해 보입니다. 간단하지만 냄새가 나지 않는 것이 있습니까?

2010 년 5 월 10 일에 추가 된 참고 사항 :

새 하위 클래스가 향후 추가 될 것이며 기존 코드가이를 정상적으로 처리해야 할 것입니다. 클래스를 찾을 수 없기 때문에 클래스의 HashMap은이 경우 작동하지 않습니다. 가장 구체적인 것으로 시작하고 가장 일반적인 것으로 끝나는 if 문 체인은 결국 가장 좋습니다.

if (obj instanceof SubClass1) {
    // Handle all the methods and properties of SubClass1
} else if (obj instanceof SubClass2) {
    // Handle all the methods and properties of SubClass2
} else if (obj instanceof Interface3) {
    // Unknown class but it implements Interface3
    // so handle those methods and properties
} else if (obj instanceof Interface4) {
    // likewise.  May want to also handle case of
    // object that implements both interfaces.
} else {
    // New (unknown) subclass; do what I can with the base class
}



답변

Steve Yegge의 Amazon 블로그 “when polymorphism fails” 에서이 항목에 관심이있을 수 있습니다 . 본질적으로 그는 다형성이 해결하는 것보다 더 많은 문제를 야기하는 이와 같은 경우를 다루고 있습니다.

문제는 다형성을 사용하려면 각 ‘스위칭’클래스의 “처리”부분의 논리를 만들어야한다는 것입니다.이 경우에는 정수 등이 있습니다. 분명히 이것은 실용적이지 않습니다. 때로는 논리적으로 코드를 넣을 올바른 장소가 아닙니다. 그는 ‘인스턴스 오브’접근 방식을 여러 가지 악 중 적은 것으로 권장합니다.

악취가 나는 코드를 작성해야하는 모든 경우와 마찬가지로 악취가 누출되지 않도록 하나의 메서드 (또는 최대 하나의 클래스)로 단추를 유지하십시오.


답변

댓글에서 강조한 바와 같이 방문자 패턴은 좋은 선택이 될 것입니다. 그러나 타겟 / 수용자 / 바이 사이트를 직접 제어하지 않으면 해당 패턴을 구현할 수 없습니다. 다음은 래퍼를 사용하여 하위 클래스를 직접 제어 할 수없는 경우에도 방문자 패턴을 계속 사용할 수있는 한 가지 방법입니다 (예 : Integer 사용).

public class IntegerWrapper {
    private Integer integer;
    public IntegerWrapper(Integer anInteger){
        integer = anInteger;
    }
    //Access the integer directly such as
    public Integer getInteger() { return integer; }
    //or method passthrough...
    public int intValue() { return integer.intValue(); }
    //then implement your visitor:
    public void accept(NumericVisitor visitor) {
        visitor.visit(this);
    }
}

물론 최종 클래스를 래핑하는 것은 그 자체의 냄새로 간주 될 수 있지만 하위 클래스와 잘 맞을 수도 있습니다. 개인적으로, 나는 instanceof여기에서 나쁜 냄새 가 아니라고 생각 합니다. 특히 그것이 한 가지 방법에 국한되어 행복하게 사용한다면 (아마도 위의 내 제안보다). 당신이 말했듯이, 그것은 매우 읽기 쉽고 형식이 안전하며 유지 관리가 가능합니다. 항상 그렇듯이 간단하게 유지하십시오.


답변

huge 대신 if처리하는 인스턴스를 맵 (key : class, value : handler)에 넣을 수 있습니다.

키별 조회가를 반환 null하면 일치하는 핸들러를 찾으려는 특수 핸들러 메서드를 호출합니다 (예 : isInstance()지도의 모든 키를 호출 ).

핸들러를 찾으면 새 키로 등록하십시오.

이것은 일반적인 경우를 빠르고 간단하게 만들고 상속을 처리 할 수있게합니다.


답변

리플렉션을 사용할 수 있습니다.

public final class Handler {
  public static void handle(Object o) {
    try {
      Method handler = Handler.class.getMethod("handle", o.getClass());
      handler.invoke(null, o);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  public static void handle(Integer num) { /* ... */ }
  public static void handle(BigDecimal num) { /* ... */ }
  // to handle new types, just add more handle methods...
}

아이디어를 확장하여 특정 인터페이스를 구현하는 하위 클래스와 클래스를 일반적으로 처리 할 수 ​​있습니다.


답변

책임 사슬 패턴을 고려할 수 있습니다. 첫 번째 예는 다음과 같습니다.

public abstract class StuffHandler {
   private StuffHandler next;

   public final boolean handle(Object o) {
      boolean handled = doHandle(o);
      if (handled) { return true; }
      else if (next == null) { return false; }
      else { return next.handle(o); }
   }

   public void setNext(StuffHandler next) { this.next = next; }

   protected abstract boolean doHandle(Object o);
}

public class IntegerHandler extends StuffHandler {
   @Override
   protected boolean doHandle(Object o) {
      if (!o instanceof Integer) {
         return false;
      }
      NumberHandler.handle((Integer) o);
      return true;
   }
}

다른 핸들러에 대해서도 비슷합니다. 그런 다음 StuffHandlers를 순서대로 묶는 경우 (최종 ‘대체’핸들러를 사용하여 가장 구체적이거나 가장 구체적이지 않음), 발송자 코드는 다음과 같습니다.firstHandler.handle(o); .

(대안은 체인을 사용하는 대신 List<StuffHandler>디스패처 클래스에 a를 가지고 handle()true를 반환 할 때까지 목록을 반복하는 것 입니다).


답변

가장 좋은 해결책은 Class를 키로, Handler를 값으로 사용하는 HashMap이라고 생각합니다. HashMap 기반 솔루션은 일정한 알고리즘 복잡성 θ (1)에서 실행되는 반면 if-instanceof-else의 냄새 체인은 선형 알고리즘 복잡성 O (N)에서 실행됩니다. 여기서 N은 if-instanceof-else 체인의 링크 수입니다. (즉, 처리 할 다른 클래스의 수). 따라서 HashMap 기반 솔루션의 성능은 if-instanceof-else 체인 솔루션의 성능보다 점근 적으로 N 배 높습니다. Message1, Message2 등 Message 클래스의 다른 하위 항목을 다르게 처리해야한다는 점을 고려하십시오. 다음은 HashMap 기반 처리를위한 코드 스 니펫입니다.

public class YourClass {
    private class Handler {
        public void go(Message message) {
            // the default implementation just notifies that it doesn't handle the message
            System.out.println(
                "Possibly due to a typo, empty handler is set to handle message of type %s : %s",
                message.getClass().toString(), message.toString());
        }
    }
    private Map<Class<? extends Message>, Handler> messageHandling =
        new HashMap<Class<? extends Message>, Handler>();

    // Constructor of your class is a place to initialize the message handling mechanism    
    public YourClass() {
        messageHandling.put(Message1.class, new Handler() { public void go(Message message) {
            //TODO: IMPLEMENT HERE SOMETHING APPROPRIATE FOR Message1
        } });
        messageHandling.put(Message2.class, new Handler() { public void go(Message message) {
            //TODO: IMPLEMENT HERE SOMETHING APPROPRIATE FOR Message2
        } });
        // etc. for Message3, etc.
    }

    // The method in which you receive a variable of base class Message, but you need to
    //   handle it in accordance to of what derived type that instance is
    public handleMessage(Message message) {
        Handler handler = messageHandling.get(message.getClass());
        if (handler == null) {
            System.out.println(
                "Don't know how to handle message of type %s : %s",
                message.getClass().toString(), message.toString());
        } else {
            handler.go(message);
        }
    }
}

Java에서 클래스 유형의 변수 사용에 대한 추가 정보 : http://docs.oracle.com/javase/tutorial/reflect/class/classNew.html


답변

instanceof로 가십시오. 모든 해결 방법이 더 복잡해 보입니다. 이에 대해 이야기하는 블로그 게시물은 다음과 같습니다. http://www.velocityreviews.com/forums/t302491-instanceof-not-always-bad-the-instanceof-myth.html