Избегание instanceof в Java

Наличие цепочки операций «instanceof» считается «запахом кода». Стандартный ответ – «polymorphism использования». Как мне это сделать в этом случае?

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

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

Оказывается, новые подclassы, вероятно, будут добавлены в будущем, и мой существующий код будет обрабатывать их изящно. В этом случае HashMap on Class не будет работать, потому что 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 Solutions collect form web for “Избегание instanceof в Java”

Возможно, вас заинтересует эта запись из блога Amazon от Steve Yegge: «Когда polymorphism терпит неудачу» . По существу он обращается к таким случаям, когда polymorphism вызывает больше проблем, чем решает.

Проблема в том, что для использования polymorphismа вы должны сделать логику «обрабатывать» часть каждого «коммутационного» classа, то есть Integer и т. Д. В этом случае. Ясно, что это непрактично. Иногда даже логически невозможно разместить код. Он рекомендует использовать «примерный» подход как меньшее из нескольких зол.

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

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

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

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

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

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

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

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

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

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

Вы можете рассмотреть схему «Цепь ответственности» . Для вашего первого примера, что-то вроде:

 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); – только firstHandler.handle(o); ,

(Альтернатива заключается в том, чтобы вместо использования цепочки просто иметь List в вашем classе диспетчера и List его через список, пока handle() вернет true).

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

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

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

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

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

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

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

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

Interesting Posts

Как далеко от экрана компьютера рекомендуется сидеть?

Ошибка bcdedit / deletevalue safeboot

Как перейти на более раннюю версию Node.js

Как установить Autoconf, Automake и соответствующие инструменты в Mac OS X из источника?

Учитывая целое число, как я могу найти следующую большую мощность из двух, используя бит-twiddling?

Подключение к общей папке Vista из Windows 7

Я не могу добавить нераспределенный раздел в мой основной раздел

Как добавить пользовательское действие WiX, которое происходит только при удалении (через MSI)?

Как изменить порядок элементов панели задач?

Как переключиться на Dvorak через несколько операционных систем

Как заставить WRAP_CONTENT работать с RecyclerView

Удалите открытый WiFi поставщика услуг рядом с моим домом

Как установить или обновить gnome v3.20.X в Kali Linux?

IPN не был отправлен, а рукопожатие не было проверено. Проверьте информацию.

64-разрядные драйверы для Windows Inspiron 1720

Давайте будем гением компьютера.