일련의 “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