简体   繁体   English

Java-通用ChangeListener

[英]Java - Generic ChangeListener

Scenario: 场景:

I have a container object which holds a mixed bag of objects all inheriting from the MyContainedObject class. 我有一个容器对象,其中包含一个混合包的对象,这些对象均从MyContainedObject类继承。 Consumers of the container class do not have direct access to contained objects, but my be interested in knowing when they change. 容器类的使用者没有直接访问所包含对象的权限,但是我想知道何时更改它们。

Design Decision: 设计决策:

What's the best way to listen for ChangeEvents on a specific class type? 在特定类类型上侦听ChangeEvent的最佳方法是什么? My initial thought is to do something with Generics. 我最初的想法是对泛型做一些事情。 For example, 例如,

private TreeMap<Class, ChangeListener> listeners;

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

When a change is detected the container class would iterate through the list and notify only listeners registered for that class type. 当检测到更改时,容器类将遍历列表,并仅通知针对该类类型注册的侦听器。

Other suggestions? 还有其他建议吗?

Thanks. 谢谢。

I'm assuming the key type on your TreeMap was meant to be a Class, not a MyContainedObject. 我假设您的TreeMap上的键类型应该是Class,而不是MyContainedObject。

If you actually need to listen for ChangeEvents on specific class types, and you want to be able to add elements to your collection even after setting listeners, this seems pretty reasonable. 如果您实际上需要在特定类类型上侦听ChangeEvents,并且希望即使在设置侦听器之后也能够向集合中添加元素,这似乎很合理。 You'll probably want to support multiple listeners for the same type, so you should either use a Multimap class ( Google Collections has some) or use a collection (probably an IdentityHashSet) for the values in your Map. 您可能需要支持同一类型的多个侦听器,因此您应该对地图中的值使用Multimap类( Google集合有一些)或使用集合(可能是IdentityHashSet)。

You may also want to add a type parameter to ChangeListener so that the listener can get the object the event fired on already casted to the appropriate type. 您可能还想向ChangeListener添加一个类型参数,以便侦听器可以将事件触发的对象转换为适当的类型。

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

You'll have to do an unchecked cast inside of your container for this to work, but it should be safe as long as your listener adding method does the right thing. 您必须在容器内进行未经检查的强制转换才能起作用,但是只要您的侦听器添加方法能够正确执行操作,它便是安全的。 eg: 例如:

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;
}

One minor nit: I'd avoid using "className" as the name of a variable that holds a Class object. 一个小技巧:我避免使用“ className”作为保存Class对象的变量的名称。 A class name is a String, typically the result of Class.getName(), etc.. It's a bit annoying, but the convention I've usually seen to avoid get around the fact that "class" is a reserved word is to misspell it as either "klass" or "cls". 类名是一个字符串,通常是Class.getName()等的结果。这有点烦人,但是我通常会避免使用这样的约定,以避免绕过“类”是保留字的事实是拼写错误它可以是“ klass”或“ cls”。

Also, if you don't need the ability to update your collection after adding listeners then I'd go with what akf suggested, as it's simpler. 另外,如果您不需要在添加侦听器后更新集合的功能,那么我会选择akf建议的内容,因为它更简单。

You could also simply have your container proxy the addChangeListener call to the contained objects in question. 您也可以简单地让您的容器将addChangeListener调用代理到所涉及的对象。 This will allow them to maintain their listener list and fire the calls as needed, without the added complexity of another level in the listener heirarchy. 这将使他们能够维护其侦听器列表并根据需要触发呼叫,而不会增加侦听器层次结构中另一个级别的复杂性。

One problem with this type-specific notification approach is that a client could potentially register a listener interested in when a particular interface had changed; 这种特定于类型的通知方法的一个问题是,当特定接口发生更改时,客户端可能会注册感兴趣的侦听器。 eg addChangeListenerForObjectsOfType(Iterable.class) . 例如addChangeListenerForObjectsOfType(Iterable.class) This means your notification algorithm couldn't do a simple look-up / in your listeners map, unless you explicitly prevented registering listeners against interfaces, but instead would need to be more complex (and less efficient). 这意味着您的通知算法无法在侦听器映射中进行简单的查找/,除非您明确禁止在接口上注册侦听器,但需要更加复杂(且效率较低)。

I would probably take a different approach rather than making your implementation fully generic. 我可能会采用其他方法,而不是使您的实现完全通用。 For example, if you could identify a handful of top-level sub-classes you're interested in you could provide a more explicit listener interface: 例如,如果您可以识别一些您感兴趣的顶级子类,则可以提供一个更明确的侦听器接口:

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

I personally prefer this as it's much more explicit for programmers implementing the interface, making the code more readable. 我个人更喜欢这样做,因为对于程序员实现接口而言,它更加明确,从而使代码更具可读性。 Obviously it might be inappropriate if you're potentially storing hundreds of different sub-classes in your map. 显然,如果您可能在地图中存储数百个不同的子类,则可能是不合适的。

You could take this one step further and provide a generic ChangeEvent<T extends MyObject> implementation to avoid downcasting within your listener callback methods. 您可以更进一步,并提供通用的ChangeEvent<T extends MyObject>实现,以避免在侦听器回调方法中向下转换。

There are a few limitations on your proposed design that may cause problems depending on what types of listeners you want to write 拟议的设计存在一些限制,这些限制可能会导致问题,具体取决于要编写的侦听器类型

  1. A listener can only register for one raw type at a time. 侦听器一次只能注册一种原始类型。 If a listener is interested for many types, or has other criteria it wants to use to decide what objects it is interested in, it would have to register multiple times. 如果侦听器对多种类型感兴趣,或者有其他条件要用来决定对哪些对象感兴趣,则它必须多次注册。 Also, the listener might not know what types are out there! 另外,侦听器可能不知道那里有什么类型!
  2. It's unclear how to handle the case where you register for a subclass of MyContainedObject, and that subclass has subclasses. 目前尚不清楚如何处理您注册MyContainedObject的子类并且该子类具有子类的情况。
  3. Changes can happen to MyContainedObject instances that the container is not aware of 容器不知道的MyContainedObject实例可能会发生更改

I suggest instead having a listener for the container, and have each MyContainedObject instance also support listeners: 我建议改为为容器提供一个侦听器,并让每个MyContainedObject实例也支持侦听器:

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

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

As Adamski suggested, if there are a limited number of subclasses of MyContainedObject, then you can make the methods more specific: 正如Adamski所建议的,如果MyContainedObject的子类数量有限,则可以使方法更具体:

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

Alternatively, you could make the specific methods in MyContainedObjectListener. 另外,您可以在MyContainedObjectListener中创建特定的方法。

If you decide that registering for a type meets your needs, then consider making the listener generic: 如果您决定注册一种类型满足您的需求,那么可以考虑使侦听器通用:

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

Either way, read the GoF section on Observer, since there are multiple implementation possibilities, each with advantages and disadvantages. 无论哪种方式,请阅读《观察者》的GoF部分,因为存在多种实现可能性,每种都有优点和缺点。

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

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