メディエーター対オブザーバーのオブジェクト指向設計パターン


93

私はギャングオブフォーを読んでいますいくつかの問題を解決するためにて、Mediatorパターンに出くわしました。

以前使用していた 、プロジェクトでいくつかのGUIアプリケーションを作成するためにObserverを。この2つに大きな違いはないので、少し混乱しています。違いを見つけるために閲覧しましたが、私のクエリに対する適切な答えを見つけることができませんでした。

2人を明確に区別するいくつかの良い例で2人を区別するのに役立つ人がいますか?


5
この質問への移行リクエストProgrammers.StackExchangeは拒否されましたが、回答に興味があったため、同様の投稿を行いました。興味深い答えがいくつか見つかるかもしれません。:)
レイチェル


オリジナルのGoFブックでは、実装セクションのポイント#8で、を使用ChangeManagerするObserverパターンのaの例を示して対処していますMediator。見る; paginas.fe.up.pt/~aaguiar/as/gof/hires/pat5g.htm#samplecode
robi-y

回答:


104

Observerパターン:オブジェクト間の1対多の依存関係を定義します。これにより、1つのオブジェクトの状態が変化すると、そのすべての依存オブジェクトが自動的に通知および更新されます。

Mediatorパターン:オブジェクトのセットがどのように相互作用するかをカプセル化するオブジェクトを定義します。Mediatorは、オブジェクトが相互に明示的に参照しないようにすることで疎結合を促進し、相互作用を個別に変化させることができます。

ソース:dofactory

例:

オブザーバーパターン:クラスAには、タイプOの0個以上のオブザーバーを登録できます。Aの何かが変更されると、すべてのオブザーバーに通知されます。

メディエーターパターン:クラスXのインスタンスがいくつかあり(または、いくつかの異なるタイプ:X、Y、Zでさえも)、お互いに通信したい(ただし、それぞれに明示的な参照を設定したくない) other)、メディエータークラスMを作成します。Xの各インスタンスには、Mの共有インスタンスへの参照があり、それを介してXの他のインスタンス(またはX、Y、Z)と通信できます。


オブザーバーの説明は、オブザーバーパターンではなくコマンドパターンに近いようです
Aun

40

ObserverとMediator、Design Patterns、Elements of Reusable Object-Oriented Softwareという用語を作り出したオリジナルの本してMediatorパターンを実装できると述べています。ただし、同僚(オブザーバーパターンのサブジェクトとほぼ同等)にMediatorクラスまたはMediatorインターフェイスへの参照を持たせることで実装することもできます。

オブザーバーパターンを使用したい場合が多くありますが、それらの重要な点は、オブジェクトは他のオブジェクトがその状態を監視していることを知らないということです。

メディエーターはもう少し具体的です。クラスが直接メディエーターを介して通信することを避けます。これにより、通信を処理するクラスだけに通信をオフロードできるようになり、単一責任の原則が役立ちます。

古典的なMediatorの例はGUIにあり、単純なアプローチでは、ボタンクリックイベントで「Fooパネルが無効で、Barパネルに「日付を入力してください」というラベルが付いている場合は、サーバーを呼び出さない」というコードが表示される可能性があります。それ以外の場合は先に進んでください。メディエーターパターンを使用すると、「私は単なるボタンであり、Fooパネルとバーパネルのラベルを知っている地上のビジネスはないので、サーバーを呼び出すかどうかをメディエーターに尋ねます今は大丈夫です。」

または、メディエーターがオブザーバーパターンを使用して実装されている場合、ボタンには「ねえ、オブザーバー(メディエーターが含まれます)、私の状態が変更されました(誰かが私をクリックしました)。メディエーターを直接参照するよりもおそらく意味が少ない私の例では、多くの場合、オブザーバーパターンを使用してメディエーターを実装することは理にかなっており、オブザーバーとメディエーターの違いは、コード自体の違いよりも意図の1つです。


私はこの言葉「単一責任原則」を探していました。
stdout 2016年

37

観察者

1.なし

  • Client1:Hey Subject、いつ変更しますか?

  • Client2件名をいつ変更しましたか?気づかなかった!

  • Client3:私は件名が変更されたことを知っています。

2.あり

  • クライアントは沈黙しています。
  • 今度いつか ...
  • 件名お客様各位、私は変わりました!

調停者

1.なし

  • クライアント1:ねえTaxi1は、いくつかの場所に私を取ります。
  • Client2:Hey Taxi1、どこかに連れて行ってくれ。
  • クライアント1:ねえTaxi2は、いくつかの場所に私を取ります。
  • Client2:Hey Taxi2、どこかに連れて行ってくれ。

2.あり

  • クライアント1:ねえTaxiCenterは、私に取ってくださいタクシーを
  • Client2:Hey TaxiCenterタクシー呼んでください。

2
メディエーターの例は、メディエーターパターンではなくファクトリーパターンです
Mohammad Karimi

2
@Pmprメディエーターのデザインパターンです。TaxiCenterはタクシーを作成せず、何らかの方法でタクシーを利用できるようにします(おそらく、各タクシーはTaxiCenterがあなたの番だと言うまで待機します)
Siva R

14

これらのパターンは、さまざまな状況で使用されます。

メディエーターパターンは、依存関係のある2つのサブシステムがあり、そのうちの1つが変更によるものである場合に使用されます。また、他のシステムに依存するシステムを変更したくないため、メディエーターを導入することもできます。それらの間の依存関係を切り離します。そうすれば、サブシステムの1つが変更されたときに、メディエーターを更新するだけで済みます。

オブザーバーパターンは、クラスが他のクラスを登録し、ButtonListenerなどのイベントの通知を受信できるようにする場合に使用されます。

これらのパターンはどちらも結合を少なくすることができますが、まったく異なります。


7

例を挙げましょう。2つのアプリケーションを作成するとします。

  1. チャットアプリケーション。
  2. 緊急救急車オペレーターアプリケーション。

調停者

チャットアプリケーションを構築するには、mediatorデザインパターンを選択します。

  • 人はいつでもチャットに参加したり、チャットから離れたりする可能性があるため、チャットしている2人の間で直接参照し続けることは意味がありません。
  • 私達はまだ二人の間のコミュニケーションを促進し、二人がチャットできるようにする必要があります。

なぜ私たちは好きmediatorですか?その定義を見てください:

メディエーターパターンを使用すると、オブジェクト間の通信がメディエーターオブジェクト内にカプセル化されます。オブジェクトは互いに直接通信しなくなり、メディエーターを介して通信します。これにより、通信するオブジェクト間の依存関係が減少し、結合が減少します。

魔法はどのように機能しますか?最初に、チャットメディエーターを作成し、personsオブジェクトをそれに登録します。これにより、すべての個人と2方向の接続が確立されます(チャットメディエーターを使用してメッセージを送信できるため、チャットメディエーターがアクセスし、チャットメディエーターがアクセスします。 personオブジェクトの受け取ったメソッドにより、彼はそれにアクセスすることもできます)

function Person(name) {
    let self = this;
    this._name = name;
    this._chat = null;

    this._receive(from, message) {        
        console.log("{0}: '{1}'".format(from.name(), message));
    }
    this._send(to, message) {
        this._chat.message(this, to, message);
    }
    return {
        receive: (from, message) => { self._receive(from, message) },
        send: (to, message) => { self._send(to, message) },
        initChat: (chat) => { this._chat = chat; },
        name: () => { return this._name; }
    }
}


function ChatMediator() {
    let self = this;
    this._persons = [];    

    return {
        message: function (from, to, message) {
            if (self._persons.indexOf(to) > -1) {
                self._persons[to].receive(from, message);
            }
        },
        register: function (person) {
            person.initChat(self);
            self._persons.push(person);
        }
        unRegister: function (person) {
            person.initChat(null);
            delete self._persons[person.name()];
        }
    }
};

//Usage:
let chat = new ChatMediator();

let colton = new Person('Colton');
let ronan = new Person('Ronan');

chat.register(colton);
chat.register(ronan);

colton.send(colton, 'Hello there, nice to meet you');
ronan.send(ronan, 'Nice to meet you to');

colton.send(colton, 'Goodbye!');
chat.unRegister(colton);

観察者

911コールアプリケーションを構築するには、observerデザインパターンを選択します。

  • 救急車の各observerオブジェクトは、緊急事態が発生したときに通知を受けることを望んでいるので、住所を運転して手助けすることができます。
  • 緊急時のオペレーターobservableは、救急車のそれぞれの参照を維持observersし、助けが必要な場合(またはイベントを生成する場合)に通知します。

なぜ私たちは好きobserverですか?その定義を見てください:

サブジェクトと呼ばれるオブジェクトは、オブザーバーと呼ばれるその依存関係のリストを維持し、通常はメソッドの1つを呼び出すことによって、状態の変化を自動的に通知します。

function AmbulanceObserver(name) {
    let self = this;
    this._name = name;
    this._send(address) {
        console.log(this._name + ' has been sent to the address: ' + address);
    }
    return {
        send: (address) => { self._send(address) },
        name: () => { return this._name; }
    }
}


function OperatorObservable() {
    let self = this;
    this._ambulances = [];    

    return {
        send: function (ambulance, address) {
            if (self._ambulances.indexOf(ambulance) > -1) {
                self._ambulances[ambulance].send(address);
            }
        },
        register: function (ambulance) {
            self._ambulances.push(ambulance);
        }
        unRegister: function (ambulance) {
            delete self._ambulances[ambulance.name()];
        }
    }
};

//Usage:
let operator = new OperatorObservable();

let amb111 = new AmbulanceObserver('111');
let amb112 = new AmbulanceObserver('112');

operator.register(amb111);
operator.register(amb112);

operator.send(amb111, '27010 La Sierra Lane Austin, MN 000');
operator.unRegister(amb111);

operator.send(amb112, '97011 La Sierra Lane Austin, BN 111');
operator.unRegister(amb112);

違い:

  1. チャットmediatorは、人物オブジェクト間の2方向の通信(送信と受信)を持ち、オペレーターobservableは1方向の通信しかできません(救急車observerに運転と終了を伝えます)。
  2. チャットmediatorは、(直接の通信ではない場合でも)人のオブジェクトがそれらの間で相互作用するようにすることができ、救急車observersはオペレーターのobservableイベントに登録するだけです。
  3. 各人物オブジェクトには、チャットへの参照があり、チャットmediatormediatorは、すべての人物への参照も保持されます。救急車observerobservableオペレーターobservableを参照しない場合、オペレーターのみがすべての救急車を参照しますobserver

3
最後のビットが役立ちます。メディエーターとオブザーバーはどちらも同じ目標を達成しますが、メディエーターは双方向通信を可能にしますが、オブザーバーは一方向にしか機能しません。
kiwicomb123

正に、それが助けてくれてうれしい
Shahar Shokrani

6

どちらも状態の変化を体系的に伝えるために使用されますが、構造的および意味的にIMOとは少し異なります。

オブザーバーは、オブジェクト自体から、特定のオブジェクトの状態変化をブロードキャストするために使用されます。したがって、変更は、それを通知する役割も果たす中央オブジェクトで発生します。ただし、メディエーターでは、状態の変化は任意のオブジェクトで発生する可能性がありますが、メディエーターからブロードキャストされます。そのため、フローに違いがあります。しかし、これがコードの動作に影響を与えるとは思いません。同じ振る舞いを達成するために、どちらかを使用できます。一方、この違いは、コードの概念的な理解に影響を与える可能性があります。

参照してください、パターンを使用する主な目的は、むしろ開発者間の共通言語を作成することです。したがって、メディエーターを見ると、単一のブローカー/ハブを介して通信して通信ノイズを減らす(またはSRPを促進する)ことを試みる複数の要素を個人的に理解しており、各オブジェクトは状態の変化を通知する機能を持つという点で同様に重要です。たとえば、空港に近づいている複数の航空機について考えてみましょう。それぞれが互いに通信するのではなく、パイロン(メディエーター)を介して通信する必要があります。(着陸時に1000機の航空機が互いに通信していると考えてください-混乱します)

ただし、オブザーバーを見ると、気になる可能性のあるいくつかの状態変化があり、特定の状態変化をリッスンするには登録/サブスクライブする必要があります。状態変化の通知を担当する中心的なオブジェクトがあります。たとえば、AからBに向かう途中の特定の空港を気にする場合は、その空港に登録して、空の滑走路があるかなどのブロードキャストイベントをキャッチできます。

それが明確であることを願っています。


5

@cdcは意図の違いをうまく説明しました。

その上にさらに情報を追加します。

オブザーバー:1つのオブジェクトのイベントを異なるオブジェクトのセット(異なるクラスのインスタンス)に通知できるようにします。

調停者:特定のクラスから作成されたオブジェクトのセット間の通信を集中化します。

dofactoryのMediatorパターンの構造:

ここに画像の説明を入力してください

Mediator:同僚間のコミュニケーションのためのインターフェースを定義します。

同僚同僚間で通信されるイベントを定義する抽象クラスです

ConcreteMediator同僚オブジェクトを調整することにより協調動作を実装し、その同僚を維持します

ConcreteColleague:他の同僚によって生成されたMediatorを介して受信した通知操作を実装します

実世界の例:

メッシュでコンピューターのネットワークを維持しているトポロジ。新しいコンピュータが追加された場合、または既存のコンピュータが削除された場合、そのネットワーク内の他のすべてのコンピュータはこれら2つのイベントを認識しているはずです。

Mediatorパターンがそれにどのように適合するかを見てみましょう。

コードスニペット:

import java.util.List;
import java.util.ArrayList;

/* Define the contract for communication between Colleagues. 
   Implementation is left to ConcreteMediator */
interface Mediator{
    public void register(Colleague colleague);
    public void unregister(Colleague colleague);
}
/* Define the contract for notification events from Mediator. 
   Implementation is left to ConcreteColleague
*/
abstract class Colleague{
    private Mediator mediator;
    private String name;

    public Colleague(Mediator mediator,String name){
        this.mediator = mediator;
        this.name = name;
    }
    public String toString(){
        return name;
    }
    public abstract void receiveRegisterNotification(Colleague colleague);
    public abstract void receiveUnRegisterNotification(Colleague colleague);    
}
/*  Process notification event raised by other Colleague through Mediator.   
*/
class ComputerColleague extends Colleague {
    private Mediator mediator;

    public ComputerColleague(Mediator mediator,String name){
        super(mediator,name);
    }
    public  void receiveRegisterNotification(Colleague colleague){
        System.out.println("New Computer register event with name:"+colleague+
        ": received @"+this);
        // Send further messages to this new Colleague from now onwards
    }
    public  void receiveUnRegisterNotification(Colleague colleague){
        System.out.println("Computer left unregister event with name:"+colleague+
        ":received @"+this);
        // Do not send further messages to this Colleague from now onwards
    }
}
/* Act as a central hub for communication between different Colleagues. 
   Notifies all Concrete Colleagues on occurrence of an event
*/
class NetworkMediator implements Mediator{
    List<Colleague> colleagues = new ArrayList<Colleague>();

    public NetworkMediator(){

    }

    public void register(Colleague colleague){
        colleagues.add(colleague);
        for (Colleague other : colleagues){
            if ( other != colleague){
                other.receiveRegisterNotification(colleague);
            }
        }
    }
    public void unregister(Colleague colleague){
        colleagues.remove(colleague);
        for (Colleague other : colleagues){
            other.receiveUnRegisterNotification(colleague);
        }
    }
}

public class MediatorPatternDemo{
    public static void main(String args[]){
        Mediator mediator = new NetworkMediator();
        ComputerColleague colleague1 = new ComputerColleague(mediator,"Eagle");
        ComputerColleague colleague2 = new ComputerColleague(mediator,"Ostrich");
        ComputerColleague colleague3 = new ComputerColleague(mediator,"Penguin");
        mediator.register(colleague1);
        mediator.register(colleague2);
        mediator.register(colleague3);
        mediator.unregister(colleague1);
    }
}

出力:

New Computer register event with name:Ostrich: received @Eagle
New Computer register event with name:Penguin: received @Eagle
New Computer register event with name:Penguin: received @Ostrich
Computer left unregister event with name:Eagle:received @Ostrich
Computer left unregister event with name:Eagle:received @Penguin

説明:

  1. イーグルは、登録イベントを通じて最初にネットワークに追加されます。Eagleが最初のメンバーであるため、他の同僚への通知はありません。
  2. ときダチョウがネットワークに追加され、イーグルが通知されます。出力の1行目は、現在レンダリングされます。
  3. ときにペンギンがネットワークに追加され、両方のイーグルダチョウが 通知されています:出力の2行目と3行目は、現在レンダリングされます。
  4. イーグルが登録解除イベントを通じてネットワークを離れたとき、ダチョウペンギンの両方に通知されています。出力の4行目と5行目がレンダリングされます。

2

方法この説明について技術的には、オブザーバーとメディエーターは同じであり、コンポーネント通信に分離された方法を提供するために使用されますが、使用方法は異なります。

obeserver サブスクライブしたコンポーネントに状態の変化(たとえば、新しいdbレコードの作成)を通知する間、登録されmediator コマンド コンポーネントをして、ビジネスロジックフローに関連する何かを実行します(パスワードのリセットのためにユーザーに電子メールを送信します)。

観察者

  • 通知のコンシューマーは、通知を受け取るためにサブスクライブする責任があります
  • 通知処理はビジネスフローの一部ではありません

調停者

  • 「出版社」と「消費者」を結びつけるために必要な明示的な登録
  • 通知処理は特定のビジネスフローの一部です
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.