it-swarm.com.de

Vermeiden von instanceof in Java

Eine Kette von "Instanz" -Operationen zu haben, wird als "Codegeruch" betrachtet. Die Standardantwort lautet "Verwende Polymorphismus". Wie würde ich das in diesem Fall machen?

Es gibt eine Reihe von Unterklassen einer Basisklasse. Keiner von ihnen steht unter meiner Kontrolle. Eine analoge Situation wäre mit den Java Klassen Integer, Double, BigDecimal usw.

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

Ich habe die Kontrolle über NumberStuff und so weiter.

Ich möchte nicht viele Codezeilen verwenden, wo ein paar Zeilen ausreichen würden. (Manchmal mache ich eine HashMap-Zuordnung von Integer.class zu einer Instanz von IntegerStuff, BigDecimal.class zu einer Instanz von BigDecimalStuff usw. Aber heute möchte ich etwas Einfacheres.)

Ich hätte gerne etwas so Einfaches wie dieses:

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

Aber Java funktioniert einfach nicht so.

Ich möchte beim Formatieren statische Methoden verwenden. Die Dinge, die ich formatiere, sind zusammengesetzt, wobei ein Thing1 ein Array Thing2s und ein Thing2 ein Array Thing1s enthalten kann. Ich hatte ein Problem, als ich meine Formatierer wie folgt implementierte:

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

Ja, ich kenne die HashMap und ein bisschen mehr Code kann das auch beheben. Aber die "Instanz von" scheint im Vergleich so lesbar und wartbar zu sein. Gibt es etwas Einfaches, das aber nicht stinkt?

Anmerkung hinzugefügt am 10.05.2010:

Es stellt sich heraus, dass wahrscheinlich in Zukunft neue Unterklassen hinzugefügt werden und mein vorhandener Code sie ordnungsgemäß handhaben muss. Die HashMap-Klasse funktioniert in diesem Fall nicht, da die Klasse nicht gefunden wird. Eine Kette von if-Anweisungen, beginnend mit den spezifischsten und endend mit den allgemeinsten, ist wahrscheinlich die beste:

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
}
95
Mark Lutton

Dieser Eintrag aus Steve Yegges Amazon-Blog könnte Sie interessieren: "Wenn der Polymorphismus versagt" . Im Grunde geht er auf Fälle wie diesen ein, in denen Polymorphismus mehr Probleme verursacht als löst.

Das Problem ist, dass Sie, um Polymorphismus zu verwenden, die Logik von "handle" zu einem Teil jeder "Switching" -Klasse machen müssen - in diesem Fall also zu Integer usw. Das ist natürlich nicht praktikabel. Manchmal ist es logischerweise nicht der richtige Ort, um den Code einzufügen. Er empfiehlt, dass der Ansatz der „Instanz“ das geringere von mehreren Übeln ist.

Wie in allen Fällen, in denen Sie gezwungen sind, stinkenden Code zu schreiben, lassen Sie ihn in einer Methode (oder höchstens einer Klasse) gedrückt, damit der Geruch nicht herausläuft.

51
DJClayworth

Wie in den Kommentaren hervorgehoben, wäre das Besuchermuster eine gute Wahl. Ohne direkte Kontrolle über das Ziel/Akzeptor/Besucher kann dieses Muster jedoch nicht implementiert werden. Hier ist eine Möglichkeit, wie das Besuchermuster möglicherweise weiterhin verwendet werden kann, obwohl Sie keine direkte Kontrolle über die Unterklassen haben, indem Sie Wrapper verwenden (am Beispiel von 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);
    }
}

Natürlich kann das Verpacken einer Abschlussklasse als Eigengeruch betrachtet werden, aber vielleicht passt es gut zu Ihren Unterklassen. Persönlich denke ich nicht, dass instanceof hier so schlecht riecht, besonders wenn es auf eine Methode beschränkt ist und ich es gerne verwenden würde (wahrscheinlich über meinen eigenen Vorschlag oben). Wie Sie sagen, es ist gut lesbar, typsicher und wartbar. Halte es wie immer einfach.

20
Chris Knight

Anstelle eines riesigen if können Sie die von Ihnen behandelten Instanzen in eine Map einfügen (Schlüssel: Klasse, Wert: Handler).

Wenn die Suche nach Schlüssel null zurückgibt, rufen Sie eine spezielle Handlermethode auf, die versucht, einen passenden Handler zu finden (z. B. indem Sie isInstance() für jeden Schlüssel in der Map aufrufen).

Wenn ein Handler gefunden wurde, registrieren Sie ihn unter dem neuen Schlüssel.

Dies macht den allgemeinen Fall schnell und einfach und ermöglicht es Ihnen, die Vererbung zu handhaben.

15
Aaron Digulla

Sie können Reflexion verwenden:

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...
}

Sie können die Idee erweitern, um Unterklassen und Klassen, die bestimmte Schnittstellen implementieren, generisch zu behandeln.

13
Jordão

Sie könnten das Muster der Verantwortungskette betrachten. Für Ihr erstes Beispiel etwas wie:

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

und dann ähnlich für Ihre anderen Handler. Dann müssen Sie die StuffHandler der Reihe nach aneinanderreihen (am spezifischsten bis am wenigsten spezifisch, mit einem endgültigen Fallback-Handler), und Ihr Absendercode lautet nur firstHandler.handle(o);.

(Eine Alternative ist, anstatt eine Kette zu verwenden, einfach ein List<StuffHandler> In Ihrer Dispatcher-Klasse zu haben und die Liste zu durchlaufen, bis handle() true zurückgibt.).

9
Cowan

Ich denke, dass die beste Lösung HashMap mit Class als Schlüssel und Handler als Wert ist. Beachten Sie, dass die auf HashMap basierende Lösung mit konstanter algorithmischer Komplexität θ (1) ausgeführt wird, während die Geruchskette von if-instanceof-else mit linearer algorithmischer Komplexität O (N) ausgeführt wird, wobei N die Anzahl der Verknüpfungen in der if-instanceof-else-Kette ist (dh die Anzahl der verschiedenen zu behandelnden Klassen). Die Leistung einer HashMap-basierten Lösung ist also asymptotisch N-mal höher als die Leistung einer if-instanceof-else-Kettenlösung. Beachten Sie, dass Sie unterschiedliche Nachkommen der Message-Klasse unterschiedlich behandeln müssen: Message1, Message2 usw. Unten finden Sie den Codeausschnitt für die HashMap-basierte Verarbeitung.

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

Weitere Informationen zur Verwendung von Variablen des Typs Class in Java: http://docs.Oracle.com/javase/tutorial/reflect/class/classNew.html

9
Serge Rogatch

Gehen Sie einfach mit dem instanceof. Alle Problemumgehungen scheinen komplizierter zu sein. Hier ist ein Blog-Beitrag, der darüber spricht: http://www.velocityreviews.com/forums/t302491-instanceof-not-ways-bad-the-instanceof-myth.html

6
Jose Martinez

Ich habe dieses Problem mit reflection gelöst (vor etwa 15 Jahren in der Ära vor Generics).

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

Ich habe eine generische Klasse (abstrakte Basisklasse) definiert. Ich habe viele konkrete Implementierungen der Basisklasse definiert. Jede konkrete Klasse wird mit className als Parameter geladen. Dieser Klassenname wird als Teil der Konfiguration definiert.

Die Basisklasse definiert den gemeinsamen Status für alle konkreten Klassen. Konkrete Klassen ändern den Status, indem sie die in der Basisklasse definierten abstrakten Regeln überschreiben.

Zu diesem Zeitpunkt kenne ich den Namen dieses Mechanismus nicht, der als reflection bekannt war.

In diesem Artikel : Map und enum sind außer Reflektion nur wenige weitere Alternativen aufgeführt.

0
Ravindra babu