web-dev-qa-db-ja.com

コールバック/コマンドとEventListener / Observerパターン

私は非同期フレームワークを設計しようとしていて、コールバックパターンとオブザーバーパターンの賛否両論であると人々が考えていることを知りたいと思っていました。

Callback pattern:

//example callback
public interface Callback{
    public void notify(MethodResult result);
}

//example method
public class Worker{
  public void doAsyncWork(Callback callback){
     //do work
     callback.notify(result);
  }
}

//example observer pattern
public interface EventListener{
   public void notify(MethodResult result);

}

public class Worker{
  private EventListener listener;
  public registerEventListener(EventListener listener){
   this.listener=listener;
  }
  public void doAsyncWork(){
     //do work
     listener.notify(result);
  }
}

私はこれらのパターンの両方を使用しているように見えるフレームワークを使用しています。 EventListenerパターンはリスナーのリストがないため、一般的なパターンではありません。これは、リスナーの優先度に関する独自のセマンティクスを持つCompositeListenerを作成し、各リスナーへのイベントの配布を処理する方法によって簡単に実装できます。各リスナーとシリアル通知の新しいスレッドを生成します。 (これは、懸念事項を適切に分離し、標準のオブザーバー/リスナーパターンの改善であるため、実際にはこれは良いアイデアだと思います)。

それぞれをいつ使用すべきかについての考えはありますか?

Thxs。

31
DD.

どちらのパターンも優れており、どちらを選択するかは、何を構築するか、およびフレームワークをどのように使用するかによって異なります。

次の典型的な作業の流れで、ある種のパブリッシュ/サブスクライブシステムを構築しようとしている場合:

  • クライアントは非同期タスクを開始し、それを忘れます
  • タスクが完了すると、複数のハンドラーが通知を受け取ります

その場合、Observerパターンは自然な選択です。フレームワークを実行しているとき、疎結合を実現するために EventBus パターンの使用も検討する必要があります。

単純な非同期実行だけが必要で、フレームワークを使用する一般的なフローは次のとおりです。

  • 非同期タスクを開始する
  • 完了したら何かする

または

  • 非同期タスクを開始する
  • 何かをする
  • それが完了するまで待って、何かをしてください

次に、単純なCallbackを使用する必要があります。

しかし、より使いやすくクリーンなAPIを実現するために、Callback抽象化を取り除き、ワーカーコードを設計して何らかのFutureを返すようにすることをお勧めします。

public interface Worker<T> {

    Future<T> doAsync();

}

そしてWorkerは次のように使用できます:

Future<Integer> future = worker.doAsync();

// some work here

Integer result = future.get(); // waits till async work is done

Futureは標準である可能性があります Java Future 。ただし、グアバライブラリの ListenableFuture を使用することをお勧めします。

23

コマンド、コールバック、オブザーバーのパターンは、セマンティクスが異なります。

  • callback-一部の操作が何らかの結果で終了したことを単一の呼び出し元に通知します
  • observer-何らかのイベント(たとえば、終了した操作)が発生したことを0からnの関係者に通知します
  • command-操作の呼び出しをオブジェクトにカプセル化することにより、ワイヤー経由で転送可能または永続可能にする

あなたの例では、コールバックとオブザーバーの両方のパターンを組み合わせて、APIの柔軟性を高めることができます。

  1. callbackパターンを使用して操作をトリガーし、トリガーされた操作が完了したことを呼び出し元に非同期で通知します。
  2. event/observerパターンを使用して、他のいくつかのコンポーネントに動作しなかった操作をトリガーする機会を通知する機会を与える操作は終了します。
31
saintedlama

コールバックパターンの方が単純なので、コールバックパターンの方が優れていると思います。つまり、コールバックパターンは予測可能であり、独自の変異状態のためにバグが発生する可能性が低くなります。この動作例は GWTがブラウザー/サーバー通信を処理する方法 です。

ただし、ジェネリックを使用することもできます。

//example callback
public interface Callback<T> {
    public void notify(T result);
}

//example method
public class Worker{
  public void doAsyncWork(Callback<SomeTypeOrOther> callback){
     //do work
     callback.notify(result);
  }
}
5
Tom

両方のパターンは、以下を除いていくつかの共通の意図を共有しています:

観察可能なパターンは複数のリスナーに通知できます。一方、コマンドパターンは、単一のコールバックハンドラーが必要な場合に最適です。

コマンドパターンでは、元に戻す操作を実装するのは簡単です。

乾杯!

  • コールバック:特定のイベントが発生したときに呼び出される関数に引数として渡される実行可能コード。関数ポインター、無名関数、リスナー/オブザーバー(オブジェクト指向パラダイム)など、さまざまなプログラミング言語でさまざまな形式で実装できます。 しばしばは単一の実行可能コードを指しますが、それは必須ではありません。反例:Android SDK(APIレベル29)のSurfaceHolderインターフェイスでは、addCallback()メソッドを使用して複数のコールバックを登録できます。
  • リスナー/オブザーバー:オブジェクト指向のパラダイムでは、特定のイベントが発生したときに呼び出される関数に引数として渡されるメソッドを持つオブジェクトです。これは、複数のコールバックの可能な実装の1つです。
  • オブザーバーパターン:オブザーバークラスをオブザーバークラスから分離するためにオブザーバー/リスナーを使用することを推奨するオブジェクト指向のソフトウェア設計パターン。

特定のコードでは、CallbackとEventListenerの唯一の違いは次のとおりです。

  • コールバックは1つだけ登録できますが、複数のEventListenerは許可されます
  • コールバック登録は非同期タスクの実行と同期されますが、EventListener登録は同期されません。

前者はより単純で、後者はより柔軟です。再利用可能なフレームワークを作成している場合は、後者がより理にかなっています。

コマンドデザインパターンは、オブジェクトでアクションを実行するために必要なすべての情報をカプセル化したものであり、イベントの通知に使用されるメカニズムとは関係ありません。

0