[英]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>
实现,以避免在侦听器回调方法中向下转换。
拟议的设计存在一些限制,这些限制可能会导致问题,具体取决于要编写的侦听器类型
我建议改为为容器提供一个侦听器,并让每个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.