イベント駆動型プログラミングのフレームワーク

いくつかの歌詞



画像

プログラムを長くすればするほど、互いに何も知らないが他の存在を前提とする多数の異種ユニット(モジュール)で構成される疎結合システムが好きになります。 そのようなシステムは、理想的にはコンストラクターのように、依存関係なく、互いに適応せずに組み立てる必要があります。 理想的には、そのようなシステムの操作中に、システムを停止せずにすべての必要なタスクを実行し、単に新しいモジュールを世界に導入するだけで(たとえば、jarをクラスパスにスローすることにより)、システムはすぐに新しいモジュールと対話を開始します。

この点で、イベント駆動(またはイベント指向 )プログラミングパラダイムは非常に魅力的に見えます。



その議論の余地のない利点は、モジュールを追加または削除するときに既存のコードを編集する必要がなく、システムがノンストップで動作し続けることです。機能の一部を取得または削除するだけです。

マイナスはプラスであることに注意してください-変更中の動作の予測不能性、およびシステム全体の制御の弱体化(誰が責任を負うのか、なぜそれが間違って機能するのですか?)-従来のプログラミングとは異なり、システムはそのような異常に即座に厳しく対応します理由を示します(目的のモジュールが見つかりませんでした)。



伝統を変える理由



ある程度まで、イベント指向プログラミングはどこにでも現れます-現代のマルチタスクOSからあらゆる種類のフレームワークまで。 すべてのイベントをリッスンする各参加者(モジュール)の代わりに、関心のあるイベントのみをサブスクライブするため、マシンリソースの消費が少なくなります。 実際、オブジェクトのメソッド呼び出しでも、配信と受信の保証がある同期送信および受信メッセージとして認識できます。 では、なぜ複雑さが増すのでしょうか?

答えは、すべてがあらゆる結果に対して機能するということです。 誰がメッセージを受信したか、何人の受信者がいるか、そして彼がまったく応答するかどうかには興味がありません。 丁寧にお知らせします。 誰も興味がない-そして大丈夫。 興味深い-それは素晴らしい、私たちは取り組んでいます。



現実



たとえば、Java Swingの同じイベントシステム。 いくつかのイベントをリッスンするには、各コンポーネントにaddXXXListener、removeXXXListenerメソッドがあります。 そのようなメッセージを送信するfireXXXEventメソッドがあります。 すべて順調です。 しかし、たとえば、同じ哲学でコンポーネントを作成します。 そして、カプセル化を維持しながら、さまざまなイベントを送信したり、それらに応答したりします。 したがって、各XXXイベント、各コンポーネントに対してこれらのメソッドを実装する必要があるたびに...



解決策



コードは常に嫌悪感に似ているため、数行に置き換えたいと思います。 私は、その結果、そのようなタスクのヘルパーを実装することを1日以上考えました。 これは、プログラム内のどこからでも呼び出すことができる静的メソッドを持つクラスになります。 それで、何が必要ですか?

まず、あらゆるイベントに対応したいと思います。 明確にするために、イベントで標準のjava.util.EventObjectインターフェイスを実装し、リスナーでjava.util.EventListenerインターフェイスを実装します。 そのため、一方では何にも制限されず、他方では、これをAWT \ Swingイベントパラダイムに接続するのは可能な限り簡単です。 次に、おそらく、イベントサブスクリプションは次のようになります。

public static synchronized void listen(final Class<? extends EventObject> event, final EventListener listener);
      
      





単純な実装は次のようになります。
  if (!Events.listeners.containsKey(event)) { Events.listeners.put(event, new ArrayList<EventListener>()); } @SuppressWarnings("unchecked") final List<EventListener> list = (List<EventListener>) Events.listeners.get(event); if (!list.contains(listener)) { list.add(listener); }
      
      







そこで、EventObjectのサブクラスのイベントに遅れないようにし、EventListenerインターフェイスを実装することを約束します(メソッドは後で定義しません)。 ただし、特定のイベントが発生した場合は必ず通知されます。



さらに、きれいに終了するために、適切なイベントの購読を解除する機能が必要です。

 public static synchronized void forget(final Class<? extends EventObject> event, final EventListener listener);
      
      





そして、簡単なコード:
  public static synchronized void forget(final Class<? extends EventObject> event, final EventListener listener) { if (Events.listeners.containsKey(event)) { Events.listeners.get(event).remove(listener); } }
      
      







また、非常にクリーンな出口についても(論争の的となっている点はありますが):

 public static synchronized <E extends EventObject> void forget(final Class<? extends EventObject> event);
      
      





そして自然なコード:
  public static synchronized <E extends EventObject> void forget(final Class<? extends EventObject> event) { if (Events.listeners.containsKey(event)) { Events.listeners.get(event).clear(); Events.listeners.remove(event); } }
      
      







終了時に良好です。 すべてのようです。 そして、いいえ、すべてではありません-特定のイベントについて多くの場所で1行に通知する必要があります。これは次のようになります。

 public static synchronized void fire(final EventObject event, final String method);
      
      





コードはシングルスレッドであることが判明しました:
  public static synchronized void fire(final EventObject event, final String method) { final Class<? extends EventObject> eventClass = event.getClass(); if (Events.listeners.containsKey(eventClass)) { for (final EventListener listener : Events.listeners.get(eventClass)) { try { listener.getClass().getMethod(method, eventClass).invoke(listener, event); } catch (final Throwable e) { e.printStackTrace(); throw new RuntimeException(e); } } } }
      
      





もちろん、実装の欠点は、処理が順番に、呼び出しスレッドで実行されることです。 ほとんどのアプリケーションでは、このアプローチで十分なはずですが、明らかに、AWTshny EventQueueまたは同様のものを介して処理を整理する価値があります。 将来のバージョンでは修正します。

もう1つの欠点は、ハンドラーで例外をスローすると、メソッド呼び出しに例外がスローされ、リスナーに通知が停止することです(1人の「悪意のない」リスナーのため、他の一部はメッセージを受信しない場合があります)。 最終バージョンでは、この動作を修正して、標準のログ記録で無視し、オプションでハンドラーで例外イベントをサブスクライブしました。



これで、メソッドを実装する代わりに、デモではすべてが次のようになります(たとえば、java.awt.event.ActionEventを選択しました)。

  final ActionListener listener = new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { System.out.println(e); } }; Events.listen(ActionEvent.class, listener);//   // final ActionEvent event = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "command"); Events.fire(event, "actionPerformed");// ActionListener.actionPerformed(ActionEvent) // Events.forget(ActionEvent.class); // 
      
      





唯一の不便な点は、Events.fireメソッドでは、メソッドの名前を文字列として指定する必要があるため、必ず1つの引数(イベントのオブジェクト)が必要になることです。 これは、異なるリスナーがメッセージに対する異なる反応のメソッドを実装し、イベントの種類に応じて、1人のリスナーでさえこれらのメソッドを複数持つことができるためです(MouseListenerは、mouseOverやmo​​useOutなどのいくつかのメソッドを定義しているようです)。

結論として、悔い改めが残っています。すべてのメソッドは静的に同期されているため、通常のコレクションをスレッドセーフなコレクションに置き換える必要があります。 作業は、(直接的なメソッド呼び出しと比較して、ナノ秒単位で)リフレクションをさらに遅くします。これは、各リスナーに対してイベントがトリガーされるサイクルで発生しますが、これは必要な悪だと思います。



曲がった、不快で不必要な



私自身、このアプローチがとても気に入ったため、ライブラリをコミュニティ( https://github.com/lure0xaos/Events.git )と共有することにしました。 あなたがそれを好まない場合、短所を置きますが、一般的に私はコメントと賢明な提案と議論の建設的な批判に喜んでいるでしょう。



All Articles