Подтвердить что ты не робот

Избегать экземпляра в Java

Мне говорили на каком-то этапе в университете (и впоследствии читали в 15 местах), что использование instanceof должно использоваться только как "последнее средство". Имея это в виду, кто-нибудь может сказать, если следующий код, который у меня есть, - последнее средство. Я посмотрел на переполнение стека, но не могу найти аналогичный сценарий - возможно, я пропустил его?

private void allocateUITweenManager() {
   for(GameObject go:mGameObjects){
      if (go instanceof GameGroup) ((GameGroup) go).setUITweenManager(mUITweenManager);
   }
}

где

  • mGameObjects - это массив, только некоторые из которых GameGroup type
  • GameGroup является подклассом абстрактного класса GameObject.
  • GameGroup использует интерфейс UITweenable, у которого есть метод setUITweenManager()
  • GameObject не использует интерфейс UITweenable

Я полагаю, что я мог бы (и, вероятно, должен) заменить GameGroup в моем коде выше с помощью UITweenable - я бы задал тот же вопрос.

Есть ли другой способ сделать это, чтобы избежать instanceof? Этот код не может терпеть неудачу, как таковой (я думаю, правильно?), Но, учитывая плохую прессу instanceof, похоже, я совершил какой-то кардинальный грех ООП где-то вдоль линии, где я использовал instanceof здесь?

Спасибо заранее!

4b9b3361

Ответ 1

Я узнал о Visitor pattern в классе Compiler в университете, я думаю, что он может применяться в вашем сценарии. Рассмотрим следующий код:

public class GameObjectVisitor {

    public boolean visit(GameObject1 obj1) { return true; }
    .
    .
    // one method for each game object
    public boolean visit(GameGroup obj1) { return true; }
}

И тогда вы можете поместить метод в интерфейс GameObject следующим образом:

public interface GameObject {

    .
    .
    public boolean visit(GameObjectVisitor visitor);
}

И затем каждый GameObject реализует этот метод:

public class GameGroup implements GameObject {

    .
    .
    .
    public boolean visit(GameObjectVisitor visitor) {
        visitor.visit(this);
    }
}

Это особенно полезно, если у вас сложная иерархия наследования GameObject. Для вашего случая ваш метод будет выглядеть следующим образом:

private void allocateUITweenManager() {

    GameObjectVisitor gameGroupVisitor = new GameObjectVisitor() {
        public boolean visit(GameGroup obj1) {
            obj1.setUITweenManager(mUITweenManager);
        }
    };

    for(GameObject go:mGameObjects){
      go.visit(gameGroupVisitor);
   }
}

Ответ 2

ИЗМЕНИТЬ

Есть две основные вещи, которые вы можете сделать здесь, чтобы освободить себя от этого конкретного экземпляра instanceof. (Каламбур?)

  • Сделайте, как предложил мой первоначальный ответ, и переместите метод, на который вы нацеливаетесь, до класса, который вы выполняете. В этом случае это не идеально, потому что метод не имеет смысла для родительского объекта и будет загрязнять его, как сказал Тед.

  • Сжимайте область объектов, которые вы выполняете, только для объектов, знакомых с целевым методом. Я думаю, что это более идеальный подход, но он не может быть работоспособен в текущей форме вашего кода.

Лично я избегаю instanceof, как чума, потому что это заставляет меня чувствовать, что я полностью что-то пропустил, но бывают случаи, когда это необходимо. Если ваш код выложен таким образом, и у вас нет возможности уменьшить объем объектов, которые вы выполняете, то instanceof, вероятно, будет работать нормально. Но это похоже на хорошую возможность увидеть, как полиморфизм может облегчить чтение и обслуживание вашего кода в будущем.

Я оставляю исходный ответ ниже, чтобы сохранить целостность комментариев.

/EDIT

Лично я не думаю, что это хорошая причина использовать instanceof. Мне кажется, что вы можете использовать какой-то полиморфизм для достижения своей цели.

Рассматривали ли вы метод setUITweenManager(...) метода GameObject? Имеет ли смысл делать это?

Если это имеет смысл, вы могли бы реализовать свою реализацию по умолчанию, а ваш GameGroup переопределить метод для выполнения того, что вы хотите. На этом этапе ваш код может выглядеть так:

private void allocateUITweenManager() {
   for(GameObject go:mGameObjects){
       go.setUITweenManager(mUITweenManager);
   }
}

Это полиморфизм в действии, но я не уверен, что это будет лучший подход для вашей текущей ситуации. Было бы более целесообразно итератировать Collection объектов UITweenable, если это возможно.

Ответ 3

Причина, по которой instanceof обескуражена, заключается в том, что в ООП мы не должны рассматривать типы объектов извне. Вместо этого, идиоматический способ заключается в том, чтобы позволить самим себе действовать с использованием переопределенных методов. В вашем случае одним из возможных решений может быть определение boolean setUITweenManager(...) на GameObject и возврат его true, если настройка менеджера возможна для определенного объекта. Однако, если этот шаблон встречается во многих местах, классы верхнего уровня могут быть полностью загрязнены. Поэтому иногда instanceof является "меньшим злом".

Проблема с этим подходом OPP заключается в том, что каждый объект должен "знать" все возможные варианты использования. Если вам нужна новая функция, которая работает с вашей иерархией классов, вы должны добавить ее к самим классам, вы не можете иметь ее где-то отдельно, как в другом модуле. Это можно решить в общих чертах, используя шаблон , как и другие. Шаблон посетителя описывает наиболее общий способ изучения объектов и становится еще более полезным в сочетании с полиморфизмом.

Обратите внимание, что другие языки (в частности, функциональные языки) используют другой принцип. Вместо того, чтобы позволить объектам "знать", как они выполняют все возможные действия, они объявляют типы данных, у которых нет методов самостоятельно. Вместо этого код, который их использует, проверяет, как они были построены с использованием соответствия шаблонов на алгебраической типы данных. Насколько мне известно, самый близкий язык Java, который имеет соответствие шаблонов, Scala. Существует интересная статья о том, как Scala реализует сопоставление образцов, которое сравнивает несколько возможных подходов: Сопоставление объектов с шаблонами. Бурак Эмир, Мартин Одерски и Джон Уильямс.

Данные в объектно-ориентированном программировании организованы в иерархию классов. Проблема объектно-ориентированного сопоставления шаблонов заключается в том, как исследовать эту иерархию извне. Обычно это связано с классификацией объектов по их типу времени выполнения, доступом к их членам или определением какой-либо другой характеристики группы объектов. В этой статье мы сравниваем шесть различных методов сопоставления шаблонов: объектно-ориентированная декомпозиция, посетители, тип-тесты/типы, типы строк, классы классов и экстракторы. Методы сравниваются по девяти критериям, касающимся краткости, ремонтопригодности и производительности. В документе представлены классы и экстракторы case как два новых метода сопоставления образцов и показано, что их комбинация хорошо работает для всех установленных критериев.

Вкратце: в OOP вы можете легко изменять типы данных (например, добавлять подклассы), но добавление новых функций (методов) требует внесения изменений во многие классы. С помощью ADT легко добавлять новые функции, но для изменения типов данных требуется изменение многих функций.

Ответ 4

Проблема с instanceof заключается в том, что вы можете перенести изменения будущей иерархии объектов. Лучше использовать шаблон стратегии для случаев, когда вы, вероятно, будете использовать instanceof. При создании решения с экземпляром вы попадаете в проблему, которую пытается решить Стратегия: для многих ifs. Некоторые ребята основали сообщество. Противоположная кампания могла бы быть шуткой, но неперехват серьезен. В долгосрочной перспективе проекты, поддерживающие 10-20 уровней if-else-if, могут быть болью. В вашем случае вам лучше создать общий интерфейс для всех объектов вашего массива и реализовать setUITweenManager для всех из них через интерфейс.

interface TweenManagerAware{
   setUITweenManager(UITweenManager manager);
}

Ответ 5

Мне всегда немного "подозрительно" смешивать объекты разных классов в одной коллекции. Было бы возможно/иметь смысл разделить единую коллекцию GameObjects на несколько коллекций, один из простых GameObjects, другой из UITweenables? (например, используйте MultiMap с ключом класса). Тогда вы можете пойти примерно так:

for (UITweenable uit : myMap.get(UITweenable.class)) {
  uit.setUITweenManager(mUITweenManager);
}

Теперь вам нужно instanceof при вставке в карту, но она лучше инкапсулирована - скрыта от кода клиента, которому не нужно знать эти детали.

p.s. Я не фанатик обо всех SW-правилах, а Google "Принцип замены Лискова".

Ответ 6

Вы можете объявить setUITweenManager в GameObject с реализацией, которая ничего не делает.

Вы можете создать метод, который возвращает итератор для всех экземпляров UITweenable в массиве экземпляров GameObject.

И есть другие подходы, которые эффективно скрывают диспетчеризацию в пределах некоторой абстракции; например шаблоны посетителей или адаптеров.


... Я совершил какой-то кардинальный грех ООП где-то вдоль линии, где я использовал instanceof здесь?

Не действительно (ИМО).

Самая худшая проблема с instanceof - это когда вы начинаете использовать ее для тестирования классов реализации. И причина, которая особенно плоха, состоит в том, что это затрудняет добавление дополнительных классов и т.д. Здесь материал instanceof UITweenable, похоже, не представляет этой проблемы, потому что UITweenable представляется более фундаментальным для дизайна.


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

Ответ 7

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

Это будет использовать еще немного памяти, а когда вы добавляете/удаляете объекты, вы должны обновлять оба контейнера, но это не должно быть заметным накладным расходами, и это позволяет вам очень эффективно выполнять цикл через все объекты.

Ответ 8

Проблема с этим подходом заключается в том, что он обычно не появляется в одном месте только в вашем коде и, следовательно, делает его более или менее болезненным для добавления в него других реализаций интерфейса в будущем. Следует ли избегать этого, зависит от вашего рассмотрения. Иногда YAGNI можно применять, это самый простой способ.

Альтернативы были предложены другими, например шаблон посетителя.

Ответ 9

У меня есть другое предположение о том, как избежать instanceof.

Если вы используете общий factory, в тот момент, когда вы создаете GameObject, вы знаете, какой конкретный тип он есть. Итак, что вы можете сделать, это передать любой GameGroup, который вы создаете наблюдаемый объект, и позволить им добавлять к нему слушателей. Он будет работать следующим образом:

public class Game {
    private void makeAGameGroup() {
        mGameObjects.add(new GameGroup(mUITweenManagerInformer));
    }

    private void allocateUITweenManager() {
        mUITweenManagerInformer.fire(mUITweenManager);
    }

    private class OurUITweenManagerInformer extends UITweenManagerInformer {
        private ArrayList<UITweenManagerListener> listeners;

        public void addUITweenManagerListener(UITweenManagerListener l) {
            listeners.add(l);
        }

        public void fire(UITweenManager next) {
            for (UITweenManagerListener l : listeners)
                l.changed(next);
        }
    }
    private OurUITweenManagerInformer mUITweenManagerInformer = new OurUITweenManagerInformer();
}

public interface UITweenManagerInformer {
    public void addUITweenManagerListener(UITweenManagerListener l);
}

public interface UITweenManagerListener {
    public void changed(UITweenManager next);
}

Что привлекает меня к этому решению:

  • Поскольку параметр UITweenManagerInformer является конструктором для GameGoup, вы не можете забыть его передать, тогда как с помощью метода экземпляра вы можете его вызвать.

  • Мне представляется интуитивно понятным, что информация, необходимая объекту (например, как GameGroup нуждается в знании текущего UITweenManager), должна передаваться как параметр конструктора - мне нравится думать об этих как предпосылки для существующего объекта. Если у вас нет знаний о текущем UITweenManager, вы не должны создавать GameGroup, и это решение обеспечивает это.

  • instanceof никогда не используется.