web-dev-qa-db-ja.com

Java。リスナーを実装するための正しいパターン

非常に一般的に、特定のオブジェクトに多くのリスナーが必要になる状況があります。たとえば、私は持っているかもしれません

class Elephant {
  public void addListener( ElephantListener listener ) { ... }
}

しかし、私にはそのような状況がたくさんあります。つまり、Tigersを持つTigerListenerオブジェクトもあります。現在、TigerListenersとElephantListenersはまったく異なります。

interface TigerListener {
  void listenForGrowl( Growl qrowl );
  void listenForMeow( Meow meow );
}

ながら

interface ElephantListener {
  void listenForStomp( String location, double intensity );
}

私は常に各動物クラスでブロードキャストメカニズムを再実装し続けなければならないことに気づき、実装は常に同じです。推奨パターンはありますか?

26
Jake

Listenerが送信できるすべてのイベントタイプに特定のメソッドを持っている代わりに、ジェネリックEventクラスを受け入れるようにインターフェイスを変更します。その後、必要に応じてEventを特定のサブタイプにサブクラス化するか、double intensityなどの状態を含めることができます。

TigerListenerとElephentListenerは、

interface TigerListener {
    void listen(Event event);
}

実際、このインターフェイスをさらにListenerにさらにリファクタリングできます。

interface Listener {
    void listen(Event event);
}

Listener実装には、関心のある特定のイベントに必要なロジックを含めることができます

class TigerListener implements Listener {
    @Overrides
    void listen(Event event) {
        if (event instanceof GrowlEvent) {
            //handle growl...
        }
        else if (event instance of MeowEvent) {
            //handle meow
        }
        //we don't care about any other types of Events
    }
}

class ElephentListener {
    @Overrides
    void listen(Event event) {
        if (event instanceof StompEvent) {
            StompEvent stomp = (StompEvent) event;
            if ("north".equals(stomp.getLocation()) && stomp.getDistance() > 10) { 
                ... 
            }
        }
    }
}

サブスクライバーとパブリッシャーの間の重要な関係は、パブリッシャーがサブスクライバーにイベントを送信できるということです。必ずしも特定のタイプのイベントを送信できるとは限りません。このタイプのリファクタリングは、そのロジックをインターフェースから特定の実装にプッシュします。 。

28
matt b

これは、ここに来てリスナーを作りたいだけの人にとって、より一般的な答えです。 CodePathから Creating Custom Listeners を要約しています。詳細な説明が必要な場合は、その記事を読んでください。

手順は次のとおりです。

1.インターフェースを定義する

これは、不明な親と通信する必要がある子クラスにあります。

public class MyClass {

    // interface
    public interface MyClassListener {
        // add whatever methods you need here
        public void onSomeEvent(String title);
    }
}

2.リスナーセッターを作成する

プライベートリスナーメンバー変数とパブリックセッターメソッドを子クラスに追加します。

public class MyClass {

    // add a private listener variable
    private MyClassListener mListener = null;

    // provide a way for another class to set the listener
    public void setMyClassListener(MyClassListener listener) {
        this.mListener = listener;
    }


    // interface from Step 1
    public interface MyClassListener {
        public void onSomeEvent(String title);
    }
}

3.リスナーイベントのトリガー

子オブジェクトは、リスナーインターフェイスのメソッドを呼び出すことができます。聞いている人がいない可能性があるため、必ずnullを確認してください。 (つまり、親クラスがリスナーのセッターメソッドを呼び出していない可能性があります。)

public class MyClass {

    public void someMethod() {
        // ...

        // use the listener in your code to fire some event
        if (mListener != null) 
            mListener.onSomeEvent("hello");
    }


    // items from Steps 1 and 2

    private MyClassListener mListener = null;

    public void setMyClassListener(MyClassListener listener) {
        this.mListener = listener;
    }

    public interface MyClassListener {
        public void onSomeEvent(String myString);
    }
}

4.親にリスナーコールバックを実装する

これで、親が子クラスで設定したリスナーを使用できるようになります。

例1

public class MyParentClass {

    private void someMethod() {

        MyClass object = new MyClass();
        object.setMyClassListener(new MyClass.MyClassListener() {
            @Override
            public void onSomeEvent(String myString) {
                // handle event
            }
        });
    }
}

例2

public class MyParentClass implements MyClass.MyClassListener {

    public MyParentClass() {
        MyClass object = new MyClass();
        object.setMyClassListener(this);
    }

    @Override
    public void onSomeEvent(String myString) {
        // handle event
    }
}
12
Suragch

あなたのインターフェースは意味論的な価値を持ち、それらが聞いているものを表現しているので、あなたはそれを正しくやっていると思います(たとえば、踏み鳴る代わりにうなり声と鳴き声)。一般的なアプローチでは、ブロードキャストコードを再利用できますが、読みやすさが失われる可能性があります。

たとえば、値の変更をリッスンするOberserverを実装するためのユーティリティである_Java.beans.PropertyChangeSupport_があります。ブロードキャストを行いますが、ドメインクラスにメソッドを実装し、PropertyChangeSupportオブジェクトに委任する必要があります。コールバックメソッド自体は意味がなく、ブロードキャストされるイベントは文字列ベースです。

_public interface PropertyChangeListener extends Java.util.EventListener {
     void propertyChange(PropertyChangeEvent evt);
}
_

もう1つは、ブロードキャストメカニズムを提供する_Java.util.Observable_ですが、それは私にとっても最高のものではありません。

ElephantListener.onStomp()が好き

3
mhaller

別のオプションは ホワイトボードパターン です。これにより、パブリッシャーとサブスクライバーの接続が切断され、どちらにもブロードキャストコードが含まれなくなります。どちらも単純にpub/subのメッセージングメカニズムを使用しており、どちらも他と直接接続していません。

これは、OSGiプラットフォームでのメッセージングの一般的なモデルです。

1
Robin