繁体   English   中英

Java-通用ChangeListener

[英]Java - Generic ChangeListener

场景:

我有一个容器对象,其中包含一个混合包的对象,这些对象均从MyContainedObject类继承。 容器类的使用者没有直接访问所包含对象的权限,但是我想知道何时更改它们。

设计决策:

在特定类类型上侦听ChangeEvent的最佳方法是什么? 我最初的想法是对泛型做一些事情。 例如,

private TreeMap<Class, ChangeListener> listeners;

public <T extends MyContainedObject> addChangeListenerForObjectsOfType(Class<T> className, ChangeListener listener)
{
   listeners.put(className, listener);
}

当检测到更改时,容器类将遍历列表,并仅通知针对该类类型注册的侦听器。

还有其他建议吗?

谢谢。

我假设您的TreeMap上的键类型应该是Class,而不是MyContainedObject。

如果您实际上需要在特定类类型上侦听ChangeEvents,并且希望即使在设置侦听器之后也能够向集合中添加元素,这似乎很合理。 您可能需要支持同一类型的多个侦听器,因此您应该对地图中的值使用Multimap类( Google集合有一些)或使用集合(可能是IdentityHashSet)。

您可能还想向ChangeListener添加一个类型参数,以便侦听器可以将事件触发的对象转换为适当的类型。

interface ChangeListener<T> {
    void changed(T obj, /* whatever */);
}

您必须在容器内进行未经检查的强制转换才能起作用,但是只要您的侦听器添加方法能够正确执行操作,它便是安全的。 例如:

public <T extends MyContainedObject> addChangeListener(Class<T> klass,
                                                       ChangeListener<? super T> listener) {
    ...
}    

private <T extends MyContainedObject> Set<ChangeListener<? super T>> getChangeListeners(T obj) {
    Set<ChangeListener<? super T>> result = new IdentityHashSet<ChangeListener<? super T>>();
    for (Map.Entry<Class<? extends MyContainedObject>, Set<ChangeListener<?>>> entry : listeners.entrySet()) {
        if (entry.getKey().isInstance(obj)) {
            // safe because signature of addChangeListener guarantees type match
            @SuppressWarnings("unchecked")
            Set<ChangeListener<? super T>> listeners =
                (Set<ChangeListener<? super T>>) entry.getValue();
            result.addAll(listeners);
        }
    }
    return result;
}

一个小技巧:我避免使用“ className”作为保存Class对象的变量的名称。 类名是一个字符串,通常是Class.getName()等的结果。这有点烦人,但是我通常会避免使用这样的约定,以避免绕过“类”是保留字的事实是拼写错误它可以是“ klass”或“ cls”。

另外,如果您不需要在添加侦听器后更新集合的功能,那么我会选择akf建议的内容,因为它更简单。

您也可以简单地让您的容器将addChangeListener调用代理到所涉及的对象。 这将使他们能够维护其侦听器列表并根据需要触发呼叫,而不会增加侦听器层次结构中另一个级别的复杂性。

这种特定于类型的通知方法的一个问题是,当特定接口发生更改时,客户端可能会注册感兴趣的侦听器。 例如addChangeListenerForObjectsOfType(Iterable.class) 这意味着您的通知算法无法在侦听器映射中进行简单的查找/,除非您明确禁止在接口上注册侦听器,但需要更加复杂(且效率较低)。

我可能会采用其他方法,而不是使您的实现完全通用。 例如,如果您可以识别一些您感兴趣的顶级子类,则可以提供一个更明确的侦听器接口:

public interface Listener {
    void classAChanged(ChangeEvent e);
    void classBChanged(ChangeEvent e);
    void classCChanged(ChangeEvent e);
}

我个人更喜欢这样做,因为对于程序员实现接口而言,它更加明确,从而使代码更具可读性。 显然,如果您可能在地图中存储数百个不同的子类,则可能是不合适的。

您可以更进一步,并提供通用的ChangeEvent<T extends MyObject>实现,以避免在侦听器回调方法中向下转换。

拟议的设计存在一些限制,这些限制可能会导致问题,具体取决于要编写的侦听器类型

  1. 侦听器一次只能注册一种原始类型。 如果侦听器对多种类型感兴趣,或者有其他条件要用来决定对哪些对象感兴趣,则它必须多次注册。 另外,侦听器可能不知道那里有什么类型!
  2. 目前尚不清楚如何处理您注册MyContainedObject的子类并且该子类具有子类的情况。
  3. 容器不知道的MyContainedObject实例可能会发生更改

我建议改为为容器提供一个侦听器,并让每个MyContainedObject实例也支持侦听器:

public interface ContainerListener {
  void itemAdded(MyContainedObject item);
  void itemRemoved(MyContainedObject item);
}

public interface MyContainedObjectListener {
  void itemChanged(MyContainedObject item);
}

正如Adamski所建议的,如果MyContainedObject的子类数量有限,则可以使方法更具体:

public interface ContainerListener {
  void circleAdded(Circle item);
  void squareAdded(Square item);
  void shapeRemoved(Shape item);
}

另外,您可以在MyContainedObjectListener中创建特定的方法。

如果您决定注册一种类型满足您的需求,那么可以考虑使侦听器通用:

public <T extends MyContainedObject> addChangeListenerFor(Class<T> className, ChangeListener<? super T> listener)

无论哪种方式,请阅读《观察者》的GoF部分,因为存在多种实现可能性,每种都有优点和缺点。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM