Является ли хорошей практикой частое использование instanceof?

Сценарий. Я пишу код, связанный с игрой. В этой игре Player (это тоже класс) имеет список Item. Есть и другие типы элементов, которые наследуются от Item, например ContainerItem, DurableItem или WeaponItem.

Очевидно, для меня очень удобно просто иметь List<Item>. Но когда я получаю предметы игроков, единственный способ определить, какой тип предмета, - это использовать ключевое слово instanceof. Я уверен, что читал, что полагаться на это - плохая практика.

Можно ли его использовать в этом случае? Или мне следует переосмыслить всю свою структуру?


person Justas S    schedule 17.06.2015    source источник
comment
Вы можете избежать необходимости использовать instanceof, если Item определяет абстрактный метод, заставляя каждый подкласс реализовывать свой собственный.   -  person NickJ    schedule 17.06.2015
comment
Наверное, переосмыслим свою структуру. Паттерн, который вы описываете, на самом деле антипаттерн.   -  person MadConan    schedule 17.06.2015
comment
Да, в целом использование instanceof является признаком плохого / плохого дизайна. Уловка заключается в использовании шаблона двойной отправки или посетителя. Поищите в сети.   -  person Jean-Baptiste Yunès    schedule 17.06.2015
comment
Было бы легче привести несколько примеров, если бы вы немного подробно рассказали, как вы используете instanceof.   -  person MadConan    schedule 17.06.2015
comment
возможный дубликат Когда используется instanceof правильное решение?   -  person dimo414    schedule 17.06.2015


Ответы (6)


Допустим, я пишу инвентарный код:

public void showInventory(List<Item> items) {
    for (Item item : items) {
        if (item instanceof ContainerItem) {
            // container display logic here
        }
        else if (item instanceof WeaponItem) {
            // weapon display logic here
        }
        // etc etc
    }
}

Это будет компилироваться и работать нормально. Но он упускает из виду ключевую идею объектно-ориентированного дизайна: Вы можете определить родительские классы для выполнения общих полезных вещей, а дочерние классы будут заполнять конкретные важные детали.

Альтернативный подход к вышеуказанному:

abstract class Item {
    // insert methods that act exactly the same for all items here

    // now define one that subclasses must fill in themselves
    public abstract void show()
}
class ContainerItem extends Item {
    @Override public void show() {
        // container display logic here instead
    }
}
class WeaponItem extends Item {
    @Override public void show() {
        // weapon display logic here instead
    }
}

Теперь у нас есть одно место, метод show(), во всех наших дочерних классах для логики отображения инвентаря. Как нам получить к нему доступ? Легкий!

public void showInventory(List<Item> items) {
    for (Item item : items) {
        item.show();
    }
}

Мы сохраняем всю логику, относящуюся к конкретным элементам, внутри определенных подклассов элементов. Это упрощает поддержку и расширение вашей кодовой базы. Это снижает когнитивную нагрузку на длинный цикл for-each в первом примере кода. И он готов show() к повторному использованию в местах, которые вы еще даже не проектировали.

person Matt Stephenson    schedule 17.06.2015
comment
Это правильный способ сделать это, но иногда этого недостаточно. Возьмем, к примеру, класс Record - у нас есть 2 вида записей - File и Directory. Обычные вещи можно абстрагировать в записи, но, допустим, у File есть метод download(). Таким образом, из Set<Record> вы не можете загружать файлы, если не знаете, что это действительно файлы. Поэтому, если вы попадаете в этот случай, полиморфизма недостаточно, но в большинстве случаев это лучшее решение, поэтому +1 за хороший ответ - person Svetlin Zarev; 17.06.2015
comment
@SvetlinZarev Вы также можете абстрагироваться от этого - реализуя методы, которые имеют смысл либо генерировать исключение UnsupportedOperationException (например, java.util.Iterator.remove), обеспечивая возврат Null-Object / Default, либо добавляя методы для запроса, если метод применим. Все дело в усилиях, а не в принципиальной невозможности. - person Durandal; 17.06.2015
comment
Я считаю плохим дизайном добавлять методы в интерфейс, просто чтобы выбросить UnsupportedOperationException. Также совершенно иная цель паттерна нулевого объекта. Да, это может сделать свою работу, но это будет больше похоже на взлом, чем на решение. Возьмем, к примеру, мой пример «Рекорд». Почему я должен засорять интерфейс Record методами, которые не применимы к каждой записи? Поступая так, я только усложняю использование API - то есть клиентам придется иметь дело с глупыми исключениями или бесполезными возвращаемыми значениями. - person Svetlin Zarev; 17.06.2015
comment
@svetlin zarev как бы реализовать метод загрузки в вашем примере? - person markus; 16.02.2018

ИМХО использование instanceof - это запах кода. Проще говоря, это делает ваш код процедурным, а не объектно-ориентированным. ОО способ сделать это - использовать шаблон посетителя.

введите описание изображения здесь

Шаблон посетителя также позволяет вам легко создавать decorator и chain of responsibility поверх него, тем самым достигая разделения задач, что приводит к более короткому, чистому и удобному для чтения и тестирования коду.

Также вам действительно нужно знать точный класс? Не можете воспользоваться полиморфизмом? В конце концов, Axe ЯВЛЯЕТСЯ Weapon так же, как Sword.

person Svetlin Zarev    schedule 17.06.2015
comment
ИМО, шаблон посетителя - отличный способ сохранить вашу работу в безопасности - никто другой не сможет понять ваш код! - person NickJ; 17.06.2015
comment
Я не согласен, шаблон viditor позволяет вам легко строить decorator и chain of responsibility поверх него для достижения separation of concerns, что делает ваш код намного проще и легче для чтения. Он также обеспечивает безопасность типов - вы не можете добавить новый подкласс T, не наткнувшись на ошибку времени компиляции, в то время как instanceoff вы обнаружите проблему уже во время выполнения. - person Svetlin Zarev; 17.06.2015
comment
Мой комментарий не был серьезным, но я неоднократно пытался разобраться в шаблонах посетителей, и у меня ничего не получалось. Мне приходилось поддерживать код с шаблоном посетителя, и это меня расстраивало. - person NickJ; 17.06.2015
comment
Что ж, мне нужно будет немного погуглить, чтобы понять этот шаблон ... Но поправьте меня, если я ошибаюсь, но разве это не похоже на сообщение @Matt Spephensons? - person Justas S; 17.06.2015
comment
Нет, это не так. Также Мэтт разместил это после меня. Он показывает вам, как использовать полиморфные свойства языка. И в основном его подход - лучшее решение проблемы, но иногда этого недостаточно - см. Мой комментарий под его постом. Мой пост посвящен OO-способу выполнения instanceof - это метод, называемый double dispatch, и он реализуется через шаблон посетителя. Но не поймите меня неправильно. Мой ответ не совпадает и не противоположен его. Это бесплатно. Вы не можете реализовать посетителя, если неправильно реализуете свою иерархию классов. - person Svetlin Zarev; 17.06.2015
comment
Я не имел в виду ничего оскорбительного. Шаблоны посетителей кажутся мне сложными, а учебники, которые я видел, были довольно похожи на его код. - person Justas S; 18.06.2015

Вам следует переосмыслить, может быть, и попытаться использовать полиморфизм для реализации вашей List<Item> идеи.

Вот несколько ссылок на вашу проблему, которые, вероятно, могут помочь:


(Ссылки из Считается ли instanceof плохой практикой? ? Если да, то при каких обстоятельствах instanceof по-прежнему предпочтительнее?)

person Valentin Montmirail    schedule 17.06.2015

Вам следует переосмыслить свою структуру, instanceof в немета-коде почти всегда является признаком анти-паттерна. Попытайтесь определить поведение, общее для всех Item (например, наличие изображения, описания и чего-то, что происходит, когда вы нажимаете на них) в Item-классе / интерфейсе, используя abstract-ключевое слово, если это уместно, а затем используйте полиморфизм, чтобы реализовать особенности.

person LionC    schedule 17.06.2015

Ничего страшного, если тебе легко понять.

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

В конце концов, все дело в том, как физически организовать наш код с несколькими измерениями задач в одномерном пространстве. Это нетривиальная проблема, это субъективно, и панацеи нет.

В частности, наша отрасль унаследовала множество практических правил, которые были созданы в прошлом веке на основе технических и экономических ограничений той эпохи. Например, набор инструментов был очень ограничен; программисты были очень дороги; приложения развивались медленно.

Некоторые из этих правил могут больше не применяться сегодня.

person ZhongYu    schedule 17.06.2015

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

С учетом сказанного, предоставленное вами описание не требует instanceof. Есть разные способы реализовать это без instanceof, и (самое главное) альтернативное решение должно быть лучше, чем использование instanceof. Не используйте решение, отличное от instanceof, чтобы избежать instanceof, потому что вы слышали, что это плохо.

Я думаю, что для вашего сценария решение, отличное от instanceof, имеет преимущества в том, что решение становится более легко расширяемым.

for (Item i: items) {
    if (ItemTypes.WEAPON_ITEM.equals(i.getType)) {
        processWeaponType(i);
    }

}

or

for (Item i: items) {
    if (WeaponItem.class.equals(i.getType)) {
        processWeaponType(i);
    }

}
person Jose Martinez    schedule 17.06.2015
comment
хорошо, но ваше решение не более читабельно, чем instanceof. С точки зрения производительности instanceof действительно быстро; это определенно быстрее, чем сравнение нашего собственного type поля; это может быть даже быстрее, чем сравнение Class с ==. - person ZhongYu; 17.06.2015
comment
Может быть, но является ли скорость желаемым результатом или дизайн и архитектура программного обеспечения в отношении этого вопроса? Я имею в виду, что мы можем избавиться от классов все вместе и просто объединить все в один длинный метод, если вы действительно считаете, что при ответе на этот вопрос стоит учесть разницу в производительности. Поскольку JIT-компилятор все равно реорганизует ваш код, трудно сказать, какой из них быстрее. - person Jose Martinez; 17.06.2015
comment
Это не лучше, чем instanceof, это маскировка, например instanceof. Ни одно из предложений не решает проблему необходимости проверять / обновлять весь клиентский код при добавлении нового дочернего типа. - person Durandal; 17.06.2015