Избегая 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 ответов:
вы можете быть заинтересованы в этой записи из блога 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
помимо отражения.