Как избежать instanceof в Java - PullRequest
95 голосов
/ 07 мая 2010

Наличие цепочки операций «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, сопоставляющий Integer.class с экземпляром IntegerStuff, BigDecimal.class с экземпляром 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» кажется таким читабельным и понятным для сравнения.Есть ли что-нибудь простое, но не вонючее?

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

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

Ответы [ 8 ]

51 голосов
/ 07 мая 2010

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

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

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

20 голосов
/ 08 мая 2010

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

15 голосов
/ 06 июня 2010

Вместо огромного if вы можете поместить экземпляры, которые вы обрабатываете, в карту (ключ: класс, значение: обработчик).

Если поиск по ключу возвращает null, вызовите специальный метод-обработчик, который пытается найти соответствующий обработчик (например, путем вызова isInstance() для каждого ключа на карте).

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

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

13 голосов
/ 14 января 2011

Вы можете использовать отражение:

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

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

9 голосов
/ 19 декабря 2014

Я думаю, что лучшее решение - это 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

9 голосов
/ 06 июня 2010

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

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 по порядку (наиболее специфичный для наименее специфичного, с конечным обработчиком 'fallback'), и ваш код отправки просто firstHandler.handle(o);.

(альтернативачем использование цепочки, просто укажите List<StuffHandler> в вашем классе диспетчера, и он будет циклически проходить по списку, пока handle() не вернет true).

6 голосов
/ 19 июля 2012

Просто иди с instanceof. Все обходные пути кажутся более сложными. Вот сообщение в блоге, в котором говорится об этом: http://www.velocityreviews.com/forums/t302491-instanceof-not-always-bad-the-instanceof-myth.html

0 голосов
/ 26 августа 2015

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

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

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

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

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

В этой статье перечислены * несколько альтернатив, кроме enum и *1015*, кроме рефлексии.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...