48

依存関係が注入された、オブザーバー パターンの重いコード (Guava のEventBusを使用) は、これらの機能を使用せずに過去に記述したコードよりもデバッグが非常に難しいことが多いことに気付きました。特に、オブザーバーコードがいつ、なぜ呼び出されているかを判断しようとする場合。

Martin Oderski とその友人たちは、「Deprecating the Observer Pattern」という特に魅力的なタイトルの長い論文を書きましたが、私はまだそれを読む時間がありません。

オブザーバー パターンのどこが悪いのか、また、そのような優秀な人々をこの論文に導くための (提案された、またはその他の) 代替案について、より優れた点を知りたいと思います。

手始めに、私はここで論文の(面白い)批評を見つけました。

4

3 に答える 3

37

論文から直接引用:

オブザーバー パターンの正確な問題を説明するために、単純でありふれた例であるマウスのドラッグから始めます。Path次の例では、オブジェクトのドラッグ操作中にマウスの動きをトレースし、画面に表示します。シンプルにするために、Scala クロージャーをオブザーバーとして使用します。

var path: Path = null
val moveObserver = { (event: MouseEvent) =>
   path.lineTo(event.position)
   draw(path)
}
control.addMouseDownObserver { event =>
   path = new Path(event.position)
   control.addMouseMoveObserver(moveObserver)
}
control.addMouseUpObserver { event =>
   control.removeMouseMoveObserver(moveObserver)
   path.close()
   draw(path)
}

上記の例は、[25] で一般的に定義されているオブザーバー パターンを議論するように、重要なソフトウェア エンジニアリング原則の印象的なラインナップに違反しています。

副作用オブザーバーは副作用を助長します。オブザーバーはステートレスであるため、ドラッグの例のように、ステート マシンをシミュレートするために複数のオブザーバーが必要になることがよくあります。上記の変数など、関係するすべてのオブザーバーがアクセスできる状態を保存する必要がありますpath

カプセル化状態変数pathがオブザーバーのスコープをエスケープすると、オブザーバー パターンはカプセル化を破ります。

構成可能性複数のオブザーバーは、単一の関心事 (または複数、次のポイントを参照) を処理するオブジェクトの緩やかなコレクションを形成します。複数のオブザーバが異なる時点で異なる時期に設置されるため、たとえば、それらを簡単に処分することはできません。

懸念事項の分離上記のオブザーバーは、マウス パスをトレースするだけでなく、描画コマンドも呼び出します。より一般的には、同じコードの場所に 2 つの異なる関心事項を含めます。多くの場合、モデル ビュー コントローラー (MVC) [30] パターンのように、パスの構築と表示の関係を分離することが望ましいです。

スケーラビリティこの例では、パスが変更されたときにそれ自体がイベントを発行するパスのクラスを作成することで、関心の分離を実現できます。残念ながら、オブザーバー パターンのデータの一貫性は保証されません。たとえば、パスの境界を表す四角形など、元のパスの変更に依存する別のイベント公開オブジェクトを作成するとします。フレーム化されたパスを描画するために、パスとその境界の両方の変更をリッスンするオブザーバーも考えてください。このオブザーバーは、境界が既に更新されているかどうかを手動で判断する必要があり、更新されていない場合は描画操作を延期します。そうしないと、画面上のフレームが間違ったサイズで表示される可能性があります (グリッチ)。

均一性さまざまなオブザーバーをインストールする方法が異なると、コードの均一性が低下します。

抽象化この例では、低レベルの抽象化が行われています。これは、マウス イベント オブザーバーをインストールするための特定のメソッド以上のものを提供する、コントロール クラスの重量のあるインターフェイスに依存しています。したがって、正確なイベント ソースを抽象化することはできません。たとえば、エスケープ キーを押すか、タッチ スクリーンやグラフィック タブレットなどの別のポインター デバイスを使用して、ユーザーがドラッグ操作を中止できるようにすることができます。

リソース管理オブザーバーのライフタイムは、クライアントによって管理される必要があります。パフォーマンス上の理由から、ドラッグ操作中にのみマウス移動イベントを観察したいと考えています。したがって、マウス移動オブザーバーを明示的にインストールおよびアンインストールする必要があり、インストールのポイント (上記のコントロール) を覚えておく必要があります。

意味論的距離最終的に、この例は理解しにくいものです。なぜなら、制御フローが逆になっているため、ボイラープレート コードが多すぎて、プログラマーの意図と実際のコードとの間の意味論的距離が長くなるからです。

【25] E.ガンマ、R.ヘルム、R.ジョンソン、J.ブリシデス。デザイン パターン: 再利用可能なオブジェクト指向ソフトウェアの要素。Addison-Wesley Longman Publishing Co., Inc.、米国マサチューセッツ州ボストン、1995 年。ISBN 0-201-63361-2。

于 2012-07-24T13:54:17.110 に答える
18

Observer パターンには、デカップリングに伴う標準的な欠点があると思います。サブジェクトはオブザーバーから分離されますが、そのソース コードを見て誰がそれを監視しているかを知ることはできません。ハードコーディングされた依存関係は、通常、読みやすく考えやすいですが、変更や再利用が難しくなります。それはトレードオフです。

この論文に関しては、Observer パターン自体ではなく、その特定の使用法について説明しています。特に、監視対象の単一オブジェクトごとに複数のステートレス Observer オブジェクト。これには、個別のオブザーバーが互いに同期する必要があるという明らかな欠点があります ( 「オブザーバーはステートレスであるため、ドラッグの例のようにステート マシンをシミュレートするために複数のオブザーバーが必要になることがよくあります。アクセス可能な状態を保存する必要があります。上記の変数パスなど、関係するすべてのオブザーバー。 ")

上記の欠点は、Observer パターン自体ではなく、この種の使用法に固有のものです。OnThisすべての、OnThat、メソッドを実装する単一の (ステートフルな!) オブザーバー オブジェクトを作成して、OnWhatever多くのステートレス オブジェクト間でステート マシンをシミュレートする問題を取り除くこともできます。

于 2012-07-24T07:28:39.593 に答える
8

私はこのトピックに慣れていないので (その特定の記事をまだ読んでいないので) 簡単に説明します。

オブザーバー パターンは直感的に間違っています。観察対象のオブジェクトは、誰が観察しているかを知っています (サブジェクト<>-オブザーバー)。これは現実に反します (イベントベースのシナリオで)。私が叫んでも、誰が聞いているのかわかりません。稲妻が床に落ちた場合... 稲妻は、落ちるまで床があることを知りません!. 何を観察できるかを知っているのはオブザーバーだけです。

この種のことが起こると、ソフトウェアはめちゃくちゃになります - 私たちの考え方に反して構築されたからです. あたかもオブジェクトが、他のオブジェクトが自分のメソッドを呼び出すことができるものを知っているかのようです。

IMO「環境」などのレイヤーは、イベントの取得と影響を受けるユーザーへの通知を担当します。(OR は、イベントとそのイベントのジェネレーターを混合します)

Event-Source (サブジェクト) は、環境に対してイベントを生成します。環境はイベントをオブザーバーに配信します。オブザーバーは、彼に影響を与える種類のイベントに登録するか、実際に環境で定義されます。どちらの可能性も理にかなっています (ただし、簡単に説明したいと思います)。

私の理解では、オブザーバーパターンは環境と主題をまとめています。

PS。抽象的なアイデアを段落に入れるのは嫌いです!:P

于 2012-12-13T09:47:05.403 に答える