Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java - Generic ChangeListener

Tags:

java

Scenario:

I have a container object which holds a mixed bag of objects all inheriting from the MyContainedObject class. 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? 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.

like image 900
javacavaj Avatar asked Mar 01 '23 14:03

javacavaj


2 Answers

I'm assuming the key type on your TreeMap was meant to be a Class, not a 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. 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.

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.

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. 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".

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.

like image 92
Laurence Gonsalves Avatar answered Mar 11 '23 16:03

Laurence Gonsalves


You could also simply have your container proxy the addChangeListener call to the contained objects in question. 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.

like image 45
akf Avatar answered Mar 11 '23 15:03

akf