ユニバーサルリスナーのレシピ

私は頻繁にSwingで多くの仕事をしているため、結果として-

さまざまなタイプとフォームのリスナー。 ただし、一部の種は他の種よりも一般的です。

以下に、作成を自動化するためのレシピを示します。

おそらく私が提案したアプローチは独創的ではありませんが、文献では見ていません。

UPDpyatigil 、同様のアプローチを説明する記事へリンクに感謝しますが、スタイルは少し異なります。



問題の本質



Swingインターフェースまたは変化するデータの複雑な構造(たとえば、オブジェクトのドメインモデル)を作成する場合、多くの場合、観察可能なオブジェクト、つまり 関心のあるすべてのサブスクライバーに変更を通知するためのインターフェイスを提供するオブジェクト。 通常、このようなオブジェクトのインターフェイスには、次のようなメソッドが含まれています。

/** *         */ public void addMyObjectListener(IMyObjectListener listener); /** *     */ public void removeMyObjectListener(IMyObjectListener listener);
      
      





IMyObjectListenerは、このオブジェクトを監視する機能を決定するインターフェイスです。たとえば:

 public interface IMyObjectListener { public void dataAdded(MyObjectEvent event); public void dataRemoved(MyObjectEvent event); public void dataChanged(MyObjectEvent event); }
      
      





オブザーバブルオブジェクトに説明されている機能を実装する場合、以下を行う必要があります。

  1. リスナー(IMyObjectListener型のオブジェクト)のリストを保持し、addMyObjectListener(IMyObjectListener)およびremoveMyObjectListener(IMyObjectListener)メソッドを実装して管理します
  2. リスナーインターフェイスで定義された各メソッドに対して、fire SomeEventタイプのメソッド(...)を提供します。 たとえば、IMyObjectListenerタイプのリスナーをサポートするには、3つの内部メソッドを実装する必要があります。
    • fireDataAdded(MyObjectEventイベント)
    • fireDataRemoved(MyObjectEventイベント)
    • fireDataChanged(MyObjectEventイベント)
    これらのメソッドはすべて同じ方法で機能し、対応するイベントに関する通知をすべての登録済みリスナーに送信します。

  3. 変更をサブスクライバーに通知する必要がある場合は、fireXXXX(...)メソッドを呼び出します。


このようなオブジェクトを1ダースまたは2ダース実装すると、非常に面倒で退屈になります。 本質的に同じコードをたくさん書く必要があります。 さらに、観察可能な動作の実装には、考慮する必要がある多くのニュアンスが含まれています。 例:



もちろん、これらすべての問題は解決でき、プログラマーはフラグ、チェック、同期などを使用してすべての落とし穴を回避することができます。 しかし、繰り返しますが、それは同じ機能(メソッドとインターフェースの名前まで)を1ダースのクラスに書くことであり、これはすべて非常に面倒です。

この記事では、プロキシクラスの動的な作成に基づいて、リスナーサポート機能をオブザーバブルから分離する方法について説明します。 結果として得られる機能は、使いやすさと型安全性を失うことなく、どのクラスにも簡単に接続できます。



ソリューションのアイデア



上記のIMyObjectListenerインターフェースと別のそのようなインターフェースを検討してください。

 public interface IListenerSupport<T> { /** *    */ public void addListener(T listener); /** *     */ public void removeListener(T listener); }
      
      





次のようにこれらのインターフェイスの両方を実装するクラスがある場合はどうなりますか:

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





次に、ターゲットクラスを次のように実装できます。

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





提案されたアプローチでは、リスナーと通知のリストを管理するためのすべてのロジックが別のクラスに移動され、ターゲットの監視可能なクラスがはるかに単純になりました。 さらに、このクラスが将来継承される予定がない場合は、fireXXXX(...)メソッドを完全に省略することができます。 それらには1行のコードしか含まれていません。これは非常に有益であり、直接使用できます。

次のサブセクションでは、このアプローチを一般的なケースに拡張し、XXXXListenerSupportのようなクラスを常に生成しない方法を示します。



一般的なレシピ



一般的な場合、動的プロキシクラスの作成に基づいて、次のアプローチが提案されます。

私は特に何も説明しません。ほとんどのJavaプログラマーにとって、ここではすべてが明確です。

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





それだけです。

リスナーインターフェイス IMyObjectListener のみを持つListenerSupportFactoryを使用して、ターゲットの監視可能クラスは次のように実装されます。

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






All Articles