2016-12-28 4 views
4

В настоящее время я пытаюсь реорганизовать свой код после того, как прочитал, что реализация предпочтительнее расширений. В настоящее время я пытаюсь создать функцию, которая добавляет объект в сцену. Для того, чтобы лучше определить, что каждый объект, есть несколько списков, таких как список для обновления, визуализации и т.д.Java "instanceof", чтобы добавить несколько списков

private List<Updatable> updatables; 
private List<Removable> removables; 
private List<Renderable> renderables; 
private List<Collidable> collidables; 

Я хочу сделать функцию в моем классе сцены, как так:

public void add(Object o) { 
    if(o instanceof Updatable) 
     updatables.add((Updatable) o); 
    if(o instanceof Removable) 
     removables.add((Removable) o); 
    if(o instanceof Renderable) 
     renderables.add((Renderable) o); 
    if(o instanceof Collidable) 
     collidables.add((Collidable) o); 
} 

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

+2

Много подобных вопросов по этому вопросу. [Например] (https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=site:stackoverflow.com+java+instanceof+code+smell) –

ответ

1

Я хотел бы сделать подобный с фабричной моделью с использованием Map

static Map<String, List> maps = new HashMap<>(); 
static { 
    maps.put(Updatable.class.getName(), new ArrayList<Updatable>()); 
    maps.put(Removable.class.getName(), new ArrayList<Removable>()); 
    maps.put(Renderable.class.getName(), new ArrayList<Renderable>()); 
    maps.put(Collidable.class.getName(), new ArrayList<Collidable>()); 
} 
public static void add(Object obj){ 
    maps.get(obj.getClass().getName()).add(obj); 
} 
+0

Это можно использовать класс 'Class' вместо класса 'String', а затем do: maps.put (Updatable.class, новый ArrayList ()) ;? Думаю, я включу этот путь. –

+1

@MichaelChoi Вы не должны принимать ответ, даже не попробовав его. Это вызовет NPE, потому что у вас есть классы реализации для этих интерфейсов, поэтому 'maps.get()' возвращает null. Он также не решает проблему класса, реализующего несколько интерфейсов, в соответствии с вашим комментарием к моему ответу. – EJP

+0

@EJP, OP не упоминает, что «обновляемый» является классом или интерфейсом, этот код будет обновляться в зависимости от варианта использования, и при использовании 'maps.get' нужно делать больше в некоторых отрицательных случаях. Это всего лишь одна из идей предложить избегать 'if'' else', чтобы проверить 'instance of' – Jerry06

2

Просто используйте перегруженный: add(Updateable u), add(Removable r), и т.д.

+1

Это похоже на более естественный подход для Java. – Sudicode

+0

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

+0

Обновление: Этот способ для меня не работает, к сожалению. Когда объект является одновременно обновляемым и съемным, тогда попытка добавления объекта вызовет «неоднозначный вызов метода». Это можно решить путем кастинга, поэтому я бы сделал scene.add ((Updatable) obj); scene.add ((Removable) obj); но это не похоже на идеальное решение. –

1

Как упоминался в комментариях, ваш случай использования несколько особенный. Updatable, Removable, Renderable и Collidable все интерфейсы. У вас есть классы, которые реализуют один или несколько из этих интерфейсов. В конечном счете, вы хотите, чтобы ваш метод add добавлял объект в каждый список интерфейса, который он реализует. (Поправьте меня, если это не так.)

Если перегрузка будет слишком многословным, вы можете сделать это с отражением, например, так:

private List<Updatable> updatables; 
private List<Removable> removables; 
private List<Renderable> renderables; 
private List<Collidable> collidables; 

@SuppressWarnings("unchecked") 
public void add(Object o) { 
    try { 
     for (Class c : o.getClass().getInterfaces()) { 
      // Changes "Updatable" to "updatables", "Removable" to "removables", etc. 
      String listName = c.getSimpleName().toLowerCase() + "s"; 

      // Adds o to the list named by listName. 
      ((List) getClass().getDeclaredField(listName).get(this)).add(o); 
     } 
    } catch (IllegalAccessException e) { 
     // TODO Handle it 
    } catch (NoSuchFieldException e) { 
     // TODO Handle it 
    } 
} 

Но если вы собираетесь использовать этот подход, обратите внимание на эти оговорки:

  1. Отражение, по своей природе, очень подвержено ошибкам во время выполнения.
  2. Вы можете столкнуться с проблемами, если ваша иерархия сложнее. Например, если у вас есть Foo implements Updatable, Removable, а также Bar extends Foo, то вызов getClass().getInterfaces() на Bar будет возвращать пустой массив. Если это похоже на ваш прецедент, вы можете вместо этого использовать ClassUtils.getAllInterfaces от Apache Commons Lang.
+0

для утверждения 2, это потому, что считается, что бар не реализует ничего, даже если Foo есть? или просто потому, что функция «.getInterfaces()» просто не возвращает интерфейсы для дочерних классов? –

+1

Это потому, что 'getInterfaces()' будет собирать только интерфейсы, на которые напрямую ссылаются инструменты. Он не будет мешать пересечению иерархии классов, так как будет использоваться 'ClassUtils.getAllInterfaces (Class) класса Apache. Однако, если вы избыточно объявляете класс Bar, поскольку 'Bar extends Foo реализует Updatable, Removable', то' getInterfaces() 'будет выбирать их. – Sudicode

0

То, что я бы ожидать, что у вас есть отдельный add метод для каждого из этих списков, например, так:

public void addUpdatable(Updatable updatable) { 
    updatables.add(updatable); 
} 

public void addRemovable(Removable removable) { 
    removables.add(removable); 
} 

// and so on 

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

Например, недоразумение (в комментариях к комментариям EJP) о том, что происходит, когда добавлен Updatable, который также добавлен Removable, показывает это отсутствие ясности. Хотя вы используете аналогичные термины как для ваших типов, так и для своего состояния, что делает его излишним, вы все равно должны указать, к какому списку (или части состояния) добавлен элемент.

В любом случае, похоже, что вы боретесь с проблемой в ее больших масштабах.Я думаю, что было бы неплохо потратить больше времени на понимание более серьезной проблемы.

+0

Можете ли вы помочь мне понять проблему? Так это проблема с текущим дизайном? Я пытаюсь сделать игру на данный момент. Для каждого игрового цикла я хочу, чтобы определенные объекты выполняли определенные функции, то есть обновление, рендеринг, удаление. Это плохое решение проблемы? если у вас есть альтернатива. Кажется, вы предполагаете, что каждый список должен быть отдельным в конце концов, но тогда как класс, как Player, будет одновременно обновляемым, визуализируемым и съемным? –

0

От @ Jerry06, лучший способ добавить экземпляр в несколько списков разных типов для меня - использовать магический приемник.

private Map<Class, List> maps = new HashMap<>(); 

«maps» используется, чтобы найти список, который вы ищите.

private <T> List<T> get(Class<T> c) { 
    return maps.get(c); 
} 

вместо использования maps.get, я просто использую get (SomeClass.class). Функция выше автоматически преобразует ее в список с правильным типом элемента. Я тогда был в состоянии сделать:

get(Updatable.class).stream().filter(Updatable::isAwake).forEach(Updatable::update); 

вместо:

updatables.stream().filter(Updatable::isAwake).forEach(Updatable::update); 

Я в основном хотел структуру, как это так, все списки могут быть обернуты красиво, а во-вторых, чтобы я мог просто сказать карты. добавьте (SomeNewClass.class, новый ArrayList()), а затем вызовите его, когда захотите.