📜 ⬆️ ⬇️

Recipe for the Universal Listener (listener)

I often work a lot with Swing and, as a result, very often I have to create
listeners of various types and forms. However, some species are more common than others.
and below I will give my recipe to automate their creation.
Perhaps the approach proposed by me is not original, but I have not seen it in literature.
UPD : thanks to pyatigil for the link to the article , which describes a similar approach, but in a slightly different style.

The essence of the problem


When creating interfaces on Swing or complex structures of changing data (for example, a domain model of objects), it is often necessary to create observable objects, i.e. Objects that provide an interface for notifying all interested subscribers of their changes. Usually in the interface of such an object there are methods like the following:
/** *         */ public void addMyObjectListener(IMyObjectListener listener); /** *     */ public void removeMyObjectListener(IMyObjectListener listener); 

where IMyObjectListener is an interface that defines the possibilities of observing this object, for example:
 public interface IMyObjectListener { public void dataAdded(MyObjectEvent event); public void dataRemoved(MyObjectEvent event); public void dataChanged(MyObjectEvent event); } 

When implementing the described functionality for an observable object, you need to:
  1. Store a list of listeners (objects of type IMyObjectListener) and manage them by implementing the addMyObjectListener (IMyObjectListener) and removeMyObjectListener (IMyObjectListener) methods
  2. For each method defined in the listener interface, provide a method of type fire SomeEvent (...). For example, to support listeners of the IMyObjectListener type, you will have to implement three internal methods:
    • fireDataAdded (MyObjectEvent event)
    • fireDataRemoved (MyObjectEvent event)
    • fireDataChanged (MyObjectEvent event)
    All these methods work in a uniform manner, sending out notifications about the corresponding event to all registered listeners.
  3. Call the fireXXXX (...) methods wherever it is necessary to notify subscribers of changes.

The implementation of a dozen or two dozen of such objects can be quite burdensome and tedious, because you have to write a lot of essentially the same code. In addition, the implementation of observable behavior contains many nuances that must be considered. For example:

All these questions, of course, are solved, and any programmer will be able to bypass all the pitfalls, using flags, checks, synchronization, etc. But when, I repeat, we are talking about writing the same functionality (up to the names of methods and interfaces) in a dozen classes, it all becomes quite burdensome.
This article describes a method for separating listener support functionality from an observable object, based on the dynamic creation of proxy classes. The resulting functionality can be easily connected to any class without losing the ease of use and type-safety.

Solution idea


Consider the IMyObjectListener interface mentioned above and another such interface:
 public interface IListenerSupport<T> { /** *    */ public void addListener(T listener); /** *     */ public void removeListener(T listener); } 

What if we would have a class that implements both of these interfaces as follows:
 class MyObjectListenerSupport implements IMyObjectListener, IListenerSupport<IMyObjectListener> { public void addListener(IMyObjectListener listener) { // todo:  listener    } public void removeListener(IMyObjectListener listener) { // todo:  listener    } public void dataAdded(MyObjectEvent event) { // todo:      dataAdded } public void dataRemoved(MyObjectEvent event) { // todo:      dataRemoved } public void dataChanged(MyObjectEvent event) { // todo:      dataChanged } } 

Then the target class could be implemented like this:
 public class MyObject { private MyObjectListenerSupport listeners = new MyObjectListenerSupport(); public void addMyObjectListener(IMyObjectListener listener) { listeners.addListener(listener); } public void removeMyObjectListener(IMyObjectListener listener) { listeners.removeListener(listener); } protected void fireDataAdded(MyObjectEvent event) { listeners.dataAdded(event); } protected void fireDataRemoved(MyObjectEvent event) { listeners.dataRemoved(event); } protected void fireDataChanged(MyObjectEvent event) { listeners.dataChanged(event); } } 

In the proposed approach, all the logic for managing the list of listeners and notifications was moved to a separate class and the observable class became much simpler. If, moreover, this class is not supposed to be further inherited, then the fireXXXX (...) methods can be omitted altogether, since they contain only one line of code that is quite informative and can be used directly.
The next subsection will show how to extend this approach to the general case and not continually produce classes like XXXLListenerSupport.

General recipe


For the general case, the following approach is proposed, based on the creation of dynamic proxy classes.
I won't describe anything particularly, for most java-programmers, everything is clear here.
 public class ListenerSupportFactory { private ListenerSupportFactory() {} @SuppressWarnings("unchecked") public static <T> T createListenerSupport(Class<T> listenerInterface) { return (T)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[] { IListenerSupport.class, listenerInterface }, new ListenerInvocationHandler<T>(listenerInterface)); } private static class ListenerInvocationHandler<T> implements InvocationHandler { private final Class<T> _listener_iface; private final Logger _log; private final List<T> _listeners = Collections.synchronizedList(new ArrayList<T>()); private final Set<String> _current_events = Collections.synchronizedSet(new HashSet<String>()); private ListenerInvocationHandler(Class<T> listenerInterface) { _listener_iface = listenerInterface; // todo: find a more sensitive class for logger _log = LoggerFactory.getLogger(listenerInterface); } @SuppressWarnings({"unchecked", "SuspiciousMethodCalls"}) public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); // (1) handle IListenerSupport methods if (method.getDeclaringClass().equals(IListenerSupport.class)) { if ("addListener".equals(methodName)) { _listeners.add( (T)args[0] ); } else if ("removeListener".equals(methodName)) { _listeners.remove( args[0] ); } return null; } // (2) handle listener interface if (method.getDeclaringClass().equals(_listener_iface)) { if (_current_events.contains(methodName)) { throw new RuleViolationException("Cyclic event invocation detected: " + methodName); } _current_events.add(methodName); for (T listener : _listeners) { try { method.invoke(listener, args); } catch (Exception ex) { _log.error("Listener invocation failure", ex); } } _current_events.remove(methodName); return null; } // (3) handle all other stuff (equals(), hashCode(), etc.) return method.invoke(this, args); } } } 

That's all.
Using ListenerSupportFactory, having only an IMyObjectListener listener interface, the observable target class is implemented as follows:
 public class MyObject { private final MyObjectListener listeners; public MyObject() { listeners = ListenerSupportFactory.createListenerSupport(MyObjectListener.class); } public void addMyObjectListener(IMyObjectListener listener) { ((IListenerSupport<MyObjectListener>)listeners).addListener(listener); } public void removeMyObjectListener(IMyObjectListener listener) { ((IListenerSupport<MyObjectListener>)listeners).removeListener(listener); } /** *  -    **/ public void someSuperBusinessMethod(SuperMethodArgs args) { // todo: perform some cool stuff here //     MyObjectEvent event = new MyObjectEvent(); //    listeners.dataAdded(event); } } 

')

Source: https://habr.com/ru/post/120643/


All Articles