私の依存関係が注入されたオブザーバーパターンの多いコード(Guavaの EventBus を使用)は、これらの機能なしで過去に作成したコードよりもデバッグが非常に困難であることに気づきました。特に、オブザーバーコードが呼び出されるタイミングと理由を特定しようとする場合。
Martin Oderskiとその友人たちは、特に魅力的なタイトル "Deprecating the Observer Pattern" を含む長い論文を書いており、まだ読む時間はありません。
私は、オブザーバーパターンの何が問題になっているのか、そしてこのような明るい人々がこの論文を書くように導くための(提案された、または他の)代替案について、もっとよく知りたいのです。
最初に、私は論文の(面白い)批評を1つ見つけました ここ 。
論文 から直接引用:
オブザーバーパターンの正確な問題を説明するために、シンプルでユビキタスな例であるマウスのドラッグから始めます。次の例では、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
など、関係するすべてのオブザーバーがアクセスできる状態を保存する必要があります。
Encapsulation状態変数path
がオブザーバーのスコープをエスケープすると、オブザーバーパターンがカプセル化を解除します。
Composability複数のオブザーバーが1つの問題(または複数、次のポイントを参照)を処理するオブジェクトの緩いコレクションを形成します。複数のオブザーバーが異なる時間に異なる場所に設置されているため、たとえば、それらをまとめて簡単に処分することはできません。
関心の分離上記のオブザーバーは、マウスパスをトレースするだけでなく描画コマンドも呼び出します。または、より一般的には、同じコードの場所に2つの異なる関心を含めます。たとえば、model-view-controller(MVC)[30]パターンのように、パスの構築と表示の懸念を分離することがしばしば望ましいです。
Scalablityこの例では、パスが変更されたときにそれ自体がイベントをパブリッシュするパスのクラスを作成することで、懸念事項の分離を実現できます。残念ながら、オブザーバーパターンのデータの一貫性は保証されません。たとえば、パスの境界を表す四角形など、元のパスの変更に依存する別のイベント発行オブジェクトを作成するとします。フレーム化されたパスを描画するために、パスとその境界の両方の変化を監視しているオブザーバーも検討してください。このオブザーバーは、境界が既に更新されているかどうかを手動で判断し、更新されていない場合は描画操作を延期する必要があります。そうしないと、ユーザーは画面上でサイズが正しくないフレーム(グリッチ)を観察する可能性があります。
Uniformity異なるオブザーバーをインストールする異なる方法は、コードの均一性を低下させます。
抽象化この例には、低レベルの抽象化があります。これは、マウスイベントオブザーバーをインストールするための特定のメソッド以上のものを提供するコントロールクラスのヘビーウェイトインターフェイスに依存しています。したがって、正確なイベントソースを抽象化することはできません。たとえば、ユーザーがエスケープキーを押してドラッグ操作を中止したり、タッチスクリーンやグラフィックタブレットなどの別のポインターデバイスを使用したりできます。
リソース管理オブザーバーのライフタイムはクライアントが管理する必要があります。パフォーマンス上の理由から、ドラッグ操作中のみマウスの移動イベントを監視する必要があります。したがって、マウス移動オブザーバーを明示的にインストールおよびアンインストールする必要があり、インストールのポイント(上記のコントロール)を覚えておく必要があります。
セマンティック距離最終的に、制御フローが反転し、プログラマーの意図間のセマンティック距離を増加させるボイラープレートコードが多すぎるため、例は理解しにくいそして実際のコード。
[25] E.ガンマ、R。ヘルム、R。ジョンソン、およびJ.ヴリサイド。設計パターン:再利用可能なオブジェクト指向ソフトウェアの要素。 Addison-Wesley Longman Publishing Co.、Inc.、Boston、MA、USA、1995。ISBN0-201-63361-2。
Observerパターンには、デカップリングに伴う標準的な欠点があると思います。サブジェクトはオブザーバーから分離されますが、ソースコードを見て、誰がそれを観察しているかを知ることはできません。通常、ハードコードされた依存関係は読みやすく、考えやすいですが、変更して再利用するのが困難です。それはトレードオフです。
論文に関しては、それはオブザーバーのパターン自体を扱うのではなく、それの特定の使用法を扱います。特に、監視対象の単一オブジェクトごとに複数のステートレスObserverオブジェクト。これには、個別のオブザーバーが互いに同期する必要があるという明らかな欠点があります("オブザーバーはステートレスなので、ドラッグの例のように、ステートマシンをシミュレートするために、オブザーバーのいくつかが必要になることがよくあります。上記の変数パスなど、関係するすべてのオブザーバーがアクセスできます。 ")
上記の欠点は、この種の使用に固有のものであり、Observerパターン自体に固有のものではありません。すべてのOnThis
、OnThat
、OnWhatever
メソッドを実装する単一の(ステートフル!)オブザーバーオブジェクトを作成して、ステートマシン全体のシミュレーションの問題を取り除くこともできます多くのステートレスオブジェクト。
私はそのトピックに慣れていない(そしてその特定の記事をまだ読んでいない)ので、簡潔にします。
オブザーバーパターンが直感的に間違っている:監視対象のオブジェクトは、誰が監視しているかを知っている(Subject <>-Observer)。それは現実に反します(イベントベースのシナリオで)。悲鳴を上げると、誰が聞いているのかわかりません。落雷が床に当たった場合...落雷は、当たるまで床があることを知りません。オブザーバーだけが何を観察できるかを知っています。
この種のことが起こると、ソフトウェアは混乱します-私たちの考え方に反して構築されたためです。それはあたかもオブジェクトが他のオブジェクトが自分のメソッドを呼び出すことができるかを知っているかのようです。
「環境」などのIMOレイヤーは、イベントの発生と影響を受ける通知を担当するレイヤーです。 (または、イベントとそのイベントのジェネレーターを組み合わせます)
イベントソース(件名)は、環境へのイベントを生成します。環境はイベントをオブザーバーに配信します。オブザーバーは、彼に影響を与える種類のイベントに登録するか、実際に環境で定義されます。両方の可能性が理にかなっています(しかし、私は簡単になりたかったです)。
私の理解では、オブザーバーパターンは環境と主題をまとめています。
PS。段落に抽象的なアイデアを入れるのは嫌いです! :P