听众应该能够删除听众吗?

时间:2015-10-06 14:31:11

标签: java

许多类使用类似于以下内容的代码来激活侦听器。

typedef enum {
   UICollectionElementCategoryCell,
   UICollectionElementCategorySupplementaryView,
   UICollectionElementCategoryDecorationView 
} UICollectionElementCategory;

这一点很好,直到侦听器尝试添加/删除侦听器。从列表内部进行的这种修改会导致- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewLayoutAttributes *attributes = (UICollectionViewLayoutAttributes *)[super layoutAttributesForItemAtIndexPath:indexPath]; if ([attributes representedElementCategory] == UICollectionElementCategoryCell) { //some good code. } return attributes; } 。我们应该处理这种情况还是应该修改听众无效?处理添加/删除侦听器的最佳方法是什么?

更新
这是一种可能的解决方案。

private List<Listener> listeners = new ArrayList<Listener>();

public void fireListener() {
  for(Listener l : listeners) l.someMethod();
}

5 个答案:

答案 0 :(得分:15)

有三种情况:

  1. 您不希望在侦听器执行期间允许修改侦听器集合:
    在这种情况下,ConcurrentModificationException是合适的。

  2. 您希望允许修改侦听器,但更改不会反映在当前运行中:
    您必须确保listeners的修改对当前运行没有影响。 CopyOnWriteArrayList可以解决问题。在使用它之前阅读API,有一些陷阱 另一个解决方案是在迭代之前复制列表。

  3. 您希望listeners的更改在当前运行中反映出来:
    最棘手的案例。使用for-each循环和迭代器在这里不起作用(正如你已经注意到的那样;-))。你必须自己跟踪变化。
    一个可能的解决方案是将监听器存储在ArrayList中并使用标准for循环遍历该列表:

    for (int i =0; i < listeners.size(); i++) { 
        Listener l = listeners.get(i);
        if (l == null) 
            continue;
        l.handleEvent();
    }  
    

    删除侦听器会将元素在数组中的位置设置为null。 新的侦听器被添加到最后,因此将在当前执行中运行 请注意,此解决方案只是一个示例,而不是线程安全! 有时需要一些维护来删除null元素,以防止列表变得过大。

  4. 由你来决定需要什么。

    我个人最喜欢的是第二个。它允许在执行时进行修改,但不会改变当前运行的行为,这会导致意外结果。

答案 1 :(得分:9)

我相信在触发时删除侦听器感觉就像代码气味,应该避免使用。

然而,尽管如此,我能尽力回答你的问题:
在我看来,在浏览时添加/删除侦听器的最佳方法是使用支持此的迭代器。

例如:

// Assuming the following:
final List<Listener> listeners = ...

final Iterator<Listener> i = listeners.iterator();
while (i.hasNext()) {
   // Must be called before you can call i.remove()
   final Listener listener = i.next(); 
   // Fire an event to the listener or whatever...

   i.remove(); // that's where the magic happens :)
}

请注意,某些Iterators 不支持Iterator#remove方法

答案 2 :(得分:5)

我想说允许听众添加/删除其他听众(或他们自己)并不是一个好主意。它表明关注点分离不佳。毕竟,为什么听众应该对调用者有所了解?你会如何测试这种紧密耦合的系统?

你可以做的事情就是让事件处理方法(在监听器中)返回一个布尔标志,指示监听器不想接收更多事件。这使得事件调度程序负责执行删除操作,它涵盖了大多数需要从侦听器中修改侦听器列表的用例。

这种方法的主要区别在于听众只是简单地说了一些关于自己的事情(即#34;我不想要更多事件和#34;)而不是与调度员的实施联系在一起。这种分离提高了可测试性,并且不会将任何一个类的内部暴露给另一个类。

public interface FooListener {
    /**
     * @return False if listener doesn't want to receive further events.
     */
    public boolean handleEvent(FooEvent event);
}

public class Dispatcher {

    private final List<FooListener> listeners;

    public void dispatch(FooEvent event) {
      Iterator<FooListener> it = listeners.iterator();
      while (it.hasNext()) {
        if (!it.next().handleEvent(event))
          it.remove();
      }
    }
}

更新:在侦听器中添加和删除其他侦听器稍微有些问题(并且应该引发更响亮的警报铃声)但是您可以遵循类似的模式:侦听器应该返回有关内容的信息需要添加/删除其他侦听器,调度员应对该信息采取行动。

但是在这种情况下,你会遇到很多边缘情况:

  • 当前事件会发生什么?
  • 是否应该将其发送给新添加的听众?
  • 是否应该发送给即将被删除的人?
  • 如果您尝试删除列表中较早的内容且该事件已发送给它,该怎么办?
  • 另外,如果侦听器X添加侦听器Y然后删除侦听器X怎么办? Y应该配合吗?

所有这些问题都来自于监听器模式本身以及它的基本假设,即列表中的所有监听器将彼此独立。处理这些案件的逻辑肯定应该放在调度员身上而不是听众身上。

更新2:在我的示例中,我使用裸布尔值来简洁,但在实际代码中,我定义了一个双值枚举类型,以使合约更明确。

答案 3 :(得分:2)

您可以先复制listeners集合,以避免可能的ConcurrentModificationException

public void fireListener() {
  Listener[] ll = listeners.toArray(new Listener[listeners.size()]);
  for(Listener l : ll) l.someMethod();
}

答案 4 :(得分:-1)

您无法添加/删除正在使用的列表/地图。在C#中有一个很好的小类叫做ConcurrentList / ConcurrentDictionary。

在Java中,这是可能的,这里有一个很好的小链接:

https://docs.oracle.com/javase/tutorial/essential/concurrency/collections.html

由于集合在循环中使用,集合将使用一段时间。如果你在循环中调用的函数中添加/删除,你会收到一个错误,因为没有Concurrent就不允许这样做。