programing

Java에서의 인스턴스 회피

firstcheck 2022. 10. 19. 22:20
반응형

Java에서의 인스턴스 회피

"인스턴스" 조작 체인을 갖는 것은 "코드 냄새"로 간주됩니다.일반적인 대답은 "다형성을 사용한다"입니다.이럴 땐 어떻게 해야 하죠?

기본 클래스에는 많은 하위 클래스가 있지만, 어느 것도 내 통제 하에 있지 않습니다.Java 클래스의 Integer, Double, Big Decimal 등에서도 같은 상황이 발생합니다.

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

저는 Number Stuff 등을 제어할 수 있습니다.

여러 줄의 코드를 사용하고 싶지 않습니다.(HashMap을 사용하여 IntegerStuff 인스턴스에 Integer.class를 매핑하거나 BigDecimal.class를 BigDecimal Stuff 인스턴스에 매핑하기도 합니다.하지만 오늘은 좀 더 심플한 것을 원합니다.)

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

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

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

포맷할 때 정적 방법을 사용하고 싶습니다.포맷하는 것은 컴포지트입니다.Thing1에는 Thing2s 어레이를 포함할 수 있고 Thing2에는 Thing1s 어레이를 포함할 수 있습니다.포메터를 다음과 같이 실장했을 때 문제가 있었습니다.

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을 알고 있습니다.코드가 조금 더 많으면 고칠 수 있어요.그러나 "인스턴스 오브"는 비교적으로 매우 읽기 쉽고 유지보수가 가능해 보입니다.간단하지만 냄새가 나지 않는 것이 있습니까?

2010년 5월 10일에 추가된 메모:

향후 새로운 서브클래스가 추가될 것으로 예상되며, 기존 코드는 그것들을 우아하게 처리해야 합니다.이 경우 클래스를 찾을 수 없기 때문에 HashMap on Class는 작동하지 않습니다.가장 구체적인 것으로 시작해서 가장 일반적인 것으로 끝나는 일련의 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 블로그에 있는 "다형성이 실패했을 때"에 관심이 있을 것입니다.본질적으로 그는 다형성이 해결보다 더 많은 문제를 야기하는 이런 사건들을 다루고 있다.

문제는 다형성을 사용하기 위해서는 각 '스위칭' 클래스의 '핸들' 논리를 만들어야 한다는 것입니다.이 경우 정수 등입니다.확실히 이것은 실용적이지 않다.때로는 논리적으로 코드를 삽입하는 것이 적절치 않을 때도 있습니다.그는 '인스턴스 오브' 접근법이 여러 폐해 중 덜하다고 추천한다.

냄새나는 코드를 작성해야 하는 모든 경우와 마찬가지로 냄새가 새지 않도록 한 가지 방법(또는 최대 한 가지 방법)으로 단추를 잠그십시오.

댓글에 강조 표시된 것처럼 방문자 패턴이 좋은 선택이 될 것입니다.그러나 타겟/접수자/방문자를 직접 제어하지 않으면 해당 패턴을 구현할 수 없습니다.여기에서는 래퍼(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특히 한 가지 방법으로만 사용할 수 있다면 (아마도 위의 제 제안으로) 여기서 나쁜 냄새가 납니다.말씀하신 대로 꽤 읽기 쉽고, 타이프도 쉽고, 유지 보수도 가능합니다.늘그그 、 ,순게게단단 。

큰큰 a a a if키: 클래스, 값: 핸들러.

에 이 반환되는 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...
}

특정 인터페이스를 구현하는 서브클래스 및 클래스를 일반적으로 처리하도록 아이디어를 확장할 수 있습니다.

Class를 키로, Handler를 값으로 하는 HashMap이 가장 좋은 솔루션이라고 생각합니다.HashMap 기반 솔루션은 일정한 알고리즘 복잡도 θ(1)로 동작하는 반면 if-instance of-else의 스니핑 체인은 선형 알고리즘 복잡도 O(N)로 동작합니다.여기서 N은 if-instance of-else 체인 내의 링크 수(즉 처리되는 다른 클래스의 수)입니다.따라서 HashMap 기반 솔루션의 성능은 if-instance of-else 체인 솔루션의 성능보다 점근적으로 N배 더 높습니다.메시지 클래스의 하위 항목마다 다르게 처리해야 합니다.Message1, Message2 등 해시맵 기반 처리 코드 스니펫을 다음에 나타냅니다.

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에서 Class 유형의 변수 사용에 대한 자세한 내용은http://http://docs.oracle.com/javase/tutorial/reflect/class/classNew.html 를 참조해 주세요.

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

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>당신의 디스패처 클래스에서, 그리고 그것이 리스트에서 루핑되도록 합니다.handle()true를 반환합니다).

그냥 예를 들어라.모든 해결 방법이 더 복잡해 보입니다.이에 대해 소개하는 블로그 투고는 다음과 같습니다.http://www.velocityreviews.com/forums/t302491-instanceof-not-always-bad-the-instanceof-myth.html

이 문제를 해결했습니다.reflection(약 15년 전 Generics 시대).

GenericClass object = (GenericClass) Class.forName(specificClassName).newInstance();

Generic Class(Abstract Base 클래스)를 1개 정의했습니다.나는 베이스 클래스의 많은 구체적인 구현을 정의해 왔다.각 콘크리트 클래스는 className을 매개 변수로 로드됩니다.이 클래스 이름은 설정의 일부로 정의됩니다.

기본 클래스는 모든 구체적인 클래스에서 공통 상태를 정의하며, 구체적인 클래스는 기본 클래스에 정의된 추상 규칙을 재정의하여 상태를 수정합니다.

그 당시에는 이 메커니즘의 이름을 알지 못합니다.reflection.

문서에는 그 밖에 몇 가지 대안이 기재되어 있습니다.Map그리고.enum반성은 차치하고

BaseClass에 클래스 이름을 반환하는 메서드를 추가합니다.특정 클래스 이름으로 메서드를 덮어씁니다.

public class BaseClass{
  // properties and methods
  public String classType(){
      return BaseClass.class.getSimpleName();
  }
}

public class SubClass1 extends BaseClass{
 // properties and methods
  @Override
  public String classType(){
      return SubClass1.class.getSimpleName();
  }
}

public class SubClass2 extends BaseClass{
 // properties and methods
  @Override
  public String classType(){
      return SubClass1.class.getSimpleName();
  }
}

이제 스위치 케이스를 다음과 같은 방법으로 사용합니다.

switch(obj.classType()){
    case SubClass1:
        // do subclass1 task
        break;
    case SubClass2:
        // do subclass2 task
        break;
}

Java 8에서 사용하는 기능:

void checkClass(Object object) {
    if (object.getClass().toString().equals("class MyClass")) {
    //your logic
    }
}

언급URL : https://stackoverflow.com/questions/2790144/avoiding-instanceof-in-java

반응형