Избегание 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 } 

Возможно, вас заинтересует эта запись из блога 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

Каковы могут быть причины отказа отказались?

Геопространственные координаты и расстояние в километрах

Сетевой адаптер в Hyper-V сбрасывается на DockerNAT

Советы по настройке домашней сети

Сколько случайных элементов перед MD5 вызывает столкновения?

Как я могу позвонить с моего ПК?

mpi: блокировка против неблокирующего

Как вставить функцию = now () в ячейку автоматически, когда я пишу в другой, и нажмите enter

Преобразование магнитного поля X, Y, Z значений из устройства в глобальную систему отсчета

Не удалось загрузить файл или сборку «Newtonsoft.Json, Version = 4.5.0.0, Culture = neutral, PublicKeyToken = 30ad4fe6b2a6aeed»

Оптимизация «игры жизни» Конвея

Преобразование данных из длинного формата в широкоформатный с несколькими столбцами измерения

Как найти следующую фиксацию в git? (ребенок / дети)

Тот же веб-сервис, работающий на другом сетевом интерфейсе

SMS-шлюз для Windows + C #

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