Избегая instanceof в Java


наличие цепочки операций " 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 и так далее.

Я не хочу использовать много строк кода, где несколько строк будет делать. (Иногда я делаю целое число отображения HashMap.класс к экземпляру IntegerStuff, BigDecimal.класс к экземпляру BigDecimalStuff и т. д. Но сегодня я хочу чего-то попроще.)

Я хотел бы что-то простое, как это:

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 и немного больше кода может исправить это. Но "instanceof" кажется таким читаемым и поддерживаемым по сравнению. Есть ли что-нибудь простое, но не вонючий?

Примечание добавлено 5/10/2010:

получается, что новые подклассы, вероятно, будут добавлены в будущем, и мой существующий код будет работать с ними корректно. 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
}
8 94

8 ответов:

вы можете быть заинтересованы в этой записи из блога Amazon Стива Егге:"когда полиморфизм не". По сути, он обращается к таким случаям, когда полиморфизм вызывает больше проблем, чем решает.

проблема в том, что для использования полиморфизма вы должны сделать логику "дескриптора" частью каждого класса "переключения" - т. е. целого числа и т. д. в этом деле. Очевидно, что это не практично. Иногда это даже не логически правильное место для размещения кода. Он рекомендует "instanceof" подход как меньшее из нескольких зол.

Как и во всех случаях, когда вы вынуждены писать вонючий код, держите его застегнутым в одном методе (или не более одного класса), чтобы запах не просачивался.

Как подчеркивается в комментариях, шаблон посетителя будет хорошим выбором. Но без прямого контроля над целью / акцептором / посетителем вы не можете реализовать этот шаблон. Вот один из способов, которым шаблон посетителя может все еще использоваться здесь, даже если у вас нет прямого контроля над подклассами с помощью оболочек (например, 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 это плохой запах здесь, особенно если он ограничен одним методом, и я бы с удовольствием использовал его (вероятно, по моему собственному предложению выше). Как вы говорите, его вполне читабельно, типобезопасно и ремонтопригодно. Как всегда, все просто.

вместо огромной 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...
}

вы можете расширить эту идею для общей обработки подклассов и классов, реализующих определенные интерфейсы.

вы могли бы рассмотреть цепочка ответственности шаблон. Для первого примера, что-то вроде:

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

Я думаю, что лучшим решением является HashMap с классом в качестве ключа и обработчиком в качестве значения. Обратите внимание, что решение на основе HashMap работает в постоянной алгоритмической сложности θ(1), в то время как цепочка запахов if-instanceof-else работает в линейной алгоритмической сложности O(N), где N-количество звеньев в цепочке if-instanceof-else (т. е. количество различных классов, которые будут обрабатываться). Таким образом, производительность решения на основе HashMap асимптотически выше в N раз, чем производительность if-instanceof-else цепное решение. Учтите, что вам нужно обрабатывать разные потомки класса Message по-разному: Message1, Message2 и т. д. . Ниже приведен фрагмент кода для обработки на основе 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);
        }
    }
}

подробнее об использовании переменных типа Class в 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

я решил эту проблему с помощью reflection (около 15 лет назад в эпоху предварительные обобщения).

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

я определил один общий класс (абстрактный базовый класс). Я определил много конкретных реализаций базового класса. Каждый конкретный класс будет загружен с className в качестве параметра. Это имя класса определяется как часть конфигурации.

базовый класс определяет общее состояние для всех конкретных классов и конкретные классы будут изменять состояние путем переопределения абстрактные правила, определенные в базовом классе.

в то время, я не знаю название этого механизма, который был известен как reflection.

еще несколько альтернатив, перечисленных в этой статьи:Map и enum помимо отражения.