Наличие цепочки операций 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.05.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
}
источник
[text](link)
для размещения ссылок в комментариях.Ответы:
Возможно, вас заинтересует эта запись из блога Стива Йегге на Amazon: «Когда полиморфизм терпит неудачу» . По сути, он занимается такими случаями, когда полиморфизм причиняет больше проблем, чем решает.
Проблема в том, что для использования полиморфизма вы должны сделать логику «обрабатывающей» части каждого «переключающего» класса - в данном случае Integer и т. Д. Ясно, что это непрактично. Иногда это даже не логически правильное место для размещения кода. Он рекомендует подход «instanceof» как меньшее из нескольких зол.
Как и во всех случаях, когда вы вынуждены писать неприятный код, держите его застегнутым в одном методе (или не более чем в одном классе), чтобы запах не просачивался.
источник
instanceof
.Monster
классу метод , в обязанности которого входит представление объекта, например: «Привет, я орк. Что ты думаешь обо мне?». Тогда мнительный эльф может судить о монстрах на основе этих «приветствий» с кодом, похожим наbool visitOrc(Orc orc) { return orc.stench()<threshold; } bool visitFlower(Flower flower) { return flower.colour==magenta; }
. Тогда единственного кода, специфичногоclass Orc { <T> T accept(MonsterVisitor<T> v) { v.visitOrc(this); } }
для монстров, хватит для проверки каждого монстра раз и навсегда.Как подчеркивается в комментариях, шаблон посетителя будет хорошим выбором. Но без прямого контроля над целью / акцептором / посетителем вы не можете реализовать этот шаблон. Вот один из способов использования шаблона посетителя здесь, даже если у вас нет прямого контроля над подклассами с помощью оболочек (например, Integer):
Конечно, упаковка финального класса может считаться отдельным запахом, но, возможно, она хорошо сочетается с вашими подклассами. Лично я не думаю
instanceof
, что здесь неприятный запах, особенно если он ограничен одним методом, и я бы с радостью использовал его (вероятно, по моему собственному предложению выше). Как вы говорите, он вполне читаемый, безопасный и ремонтопригодный. Как всегда, будьте проще.источник
instanceof
чтобы использовать для вызова правильногоhandle()
метода, теперь вам придется использовать его для вызова правильногоXWrapper
конструктора ...Вместо огромного размера
if
вы можете поместить обрабатываемые вами экземпляры на карту (ключ: класс, значение: обработчик).Если поиск по ключу возвращается
null
, вызовите специальный метод обработчика, который пытается найти соответствующий обработчик (например, вызываяisInstance()
каждый ключ на карте).Когда обработчик найден, зарегистрируйте его под новым ключом.
Это делает общий случай быстрым и простым и позволяет обрабатывать наследование.
источник
Вы можете использовать отражение:
Вы можете расширить идею общей обработки подклассов и классов, реализующих определенные интерфейсы.
источник
if then else
цепочкой, чтобы добавлять, удалять или изменять обработчики. Код менее уязвим для изменений. Поэтому я бы сказал, что по этой причине он превосходитinstanceof
подход. В любом случае, я просто хотел предложить действительную альтернативу.getMethod(String name, Class<?>... parameterTypes)
? В противном случае я бы заменил==
наisAssignableFrom
для проверки типа параметра.Вы могли бы рассмотреть шаблон «Цепочка ответственности» . Для вашего первого примера что-то вроде:
а затем аналогично для других ваших обработчиков. Затем это случай объединения 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.
Дополнительная информация об использовании переменных типа 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 лет назад в эпоху до Generics).Я определил один общий класс (абстрактный базовый класс). Я определил много конкретных реализаций базового класса. Каждый конкретный класс будет загружен с параметром className в качестве параметра. Это имя класса определяется как часть конфигурации.
Базовый класс определяет общее состояние для всех конкретных классов, а конкретные классы будут изменять состояние, переопределяя абстрактные правила, определенные в базовом классе.
В то время я не знал названия этого механизма, который был известен как
reflection
.В этой статье перечислены еще несколько альтернатив :
Map
иenum
помимо рефлексии.источник
GenericClass
interface
Добавьте метод в BaseClass, который возвращает имя класса. И переопределите методы с определенным именем класса
Теперь используйте корпус переключателя следующим образом:
источник