大衛的Design Patterns學習筆記17:Mediator

一、概述
Mediator(中介者)模式的名稱已經基本能夠反映該模式的意圖:用一箇中介對象來封裝一系列的對象之間的交互。中介者使各對象不需要顯式地相互引用,從而使其耦合鬆散,而且可以獨立地改變它們之間的交互。Mediator很象十字路口的紅綠燈,每個車輛只需和紅綠燈交互,而不是在各車輛之間進行交互。

Mediator與Proxy的比較
Proxy模式是簡化的Mediator模式?
個人認爲從功能上講,Mediator模式與Proxy模式存在一定的相似性,但Proxy主要在於控制對被訪問對象的訪問,這種訪問往往是單向的,而Mediator模式則在於爲系統內不同的對象之間的訪問提供一種媒介,這種訪問往往是多向的。

Mediator與Facade的比較
很多設計模式的書中都會將Mediator模式與Facade模式進行比較,個人認爲二者的區別十分明顯:一個是爲整個子系統對外提供一個簡單化的接口,子系統的內部可能十分複雜,很多類交互作用才形成了最終的簡單化的統一接口;而Mediator模式則作爲系統內多個對象交互的“媒介”,負責封裝對象間的交互,所有對象只需要與Mediator類交互,而不是相互之間直線聯繫。所以,也許在實現Facade模式時在子系統的內部採用Mediator模式可能是個不錯的選擇。

二、結構
Mediator模式的結構如下圖所示:

1、Mediator模式類圖示意
上面的類圖並不能完全反映Mediator模式的全部思想,下面結合GoF的DP一書中的一個對象圖對此進行深入說明:

2、一個典型的使用Mediator模式的對象圖
在上面的類圖中,各角色的分工如下:
1
、Mediator(中介者):中介者定義一個接口用於與各Colleague(同事)對象通信。
2
、ConcreteMediator(具體中介者):具體中介者通過協調各同事對象實現協作行爲,並瞭解和維護它的各個同事。
3
、Colleague class(同事類):每一個同事類都知道它的中介者對象,每一個同事對象在需與其他的同事通信的時候,與它的中介者通信。

三、應用
各個對象之間的交互操作非常多,每個對象的行爲操作都依賴彼此對方,修改一個對象的行爲,同時會涉及到修改很多其他對象的行爲,如果使用Mediator模式,可以使各個對象間的耦合鬆散,只需關心和 Mediator的關係,使多對多的關係變成了一對多的關係,可以降低系統的複雜性,提高可修改擴展性。

在下列情況下使用中介者模式:
1
、一組對象以定義良好但是複雜的方式進行通信。產生的相互依賴關係結構混亂且難以理解。
2
、一個對象引用其他很多對象並且直接與這些對象通信,導致難以複用該對象。
3
、想定製一個分佈在多個類中的行爲,而又不想生成太多的子類。

四、優缺點
Mediator模式有以下優點和缺點:
1
、減少了子類生成Mediator將原本分佈於多個對象間的行爲集中在一起,改變這些行爲只需生成Mediator的子類即可,這樣各個Colleague類可被重用。
2
、它將各Colleague解耦。Mediator有利於各Colleague間的鬆耦合,你可以獨立的改變和複用各Colleague類和Mediator類。
3
、它簡化了對象協議用Mediator和各Colleague間的一對多的交互來代替多對多的交互。一對多的關係更易於理解、維護和擴展。
4
、它對對象如何協作進行了抽象將中介作爲一個獨立的概念並將其封裝在一個對象中,使你將注意力從對象各自本身的行爲轉移到它們之間的交互上來。這有助於弄清楚一個系統中的對象是如何交互的。
5
、它使控制集中化中介者模式將交互的複雜性變爲中介者的複雜性。因爲中介者封裝了協議,它可能變得比任一個Colleague都複雜。這可能使得中介者自身成爲一個難於維護的龐然大物。

五、舉例
Mediator模式是GoF的23種設計模式中較爲容易理解的一個,我們在平時的應用中也會經常用到,但可能不會有意去抽象出一個完整的Mediator類,因爲要設計一個可複用又可擴展的Mediator是很不容易的。如在MFC的文檔/視圖程序中,我們可能會在CWinApp派生類中添加一些供所有線程共享的數據成員或方法,這樣,各線程即可通過與theApp進行交互即可達到影響其它線程的目的,在這裏,CWinApp一定程度上扮演了Mediator的角色。

有人稱MFC程序中Dialog在各控件間扮演着Mediator的角色,認爲各控件實現時無需知道其它控件,只是將消息發送給父窗口Dialog來處理,而Dialog負責在消息處理函數中完成各控件之間的信息交互,使得控件間複雜的耦合關係變成簡單的控件-Mediator(Dialog)關係。我並非十分贊同這種觀點,因爲我們往往將控件作爲整個Dialog的一個組成部分來看待,而有意無意地忽略了它本身也是一個獨立對象的事實,造成這種現象的一個關鍵原因可能在於MFC的消息映射機制屏蔽了控件將消息發送給對話框的過程,使得我們更願意去認爲消息是Dialog(處理)的消息,而不是(來自)控件的消息。
在Java應用中,以上狀況依然存在,但是由於沒有了直接的消息映射支持,JDialog往往更容易表現得像一個Mediator。
此外,還有一個觀點認爲,Java的Container通過選擇一定的LayoutManager策略(Strategy,後面的筆記中將講到),在各控件間起到了Mediator的作用,當新的Component被加到Container,如JPanel中時,必然會對其它控件的Layout(佈局)造成影響,但各控件間是不知道對方的,他們只需知道自己的Parent Window,然後與之交互即可。這種觀點也有欠妥當,但從Mediator作爲各object之間的交互的媒介這一實質來看,也無可厚非。

簡而言之,Mediator模式的主要作用在於爲多個對象之間的交互提供一種媒介,以簡化對象之間的耦合關係。下面用兩個實例來說明Mediator模式的應用:
1
、ChatRoom:
聊天室大家想必都知道,在下面的示例中,我們嘗試實現一個布告欄式的ChatRoom,任何人想要發送消息給其他人,只需將消息發送給ChatRoom對象,由ChatRoom負責數據的轉發,而不是直接與消息的接收者交互。後面的筆記中將從Observer模式的角度來解決ChatRoom問題。

#include <iostream>
#include <string>
#include <map>
using namespace std;

class
 Participant;

// "AbstractMediator"
struct IChatroom
{

  // Methods
  virtual void Register( Participant* participant ) = 0;
  virtual
 void Send( string& from, string& to, string& message ) = 0;
};


// "AbstractColleague"
class Participant
{

    friend class
 Chatroom;
    // Fields
private:
    IChatroom* pChatroom;
    string name;

    // Constructors
public:
    /*Participant()
    {
    }*/


    Participant( const char* name )
    {

        this
->name = name;
    }


    virtual
 ~Participant()
    {
    }


    // Methods
    virtual void Send( string& to, string& message )
    {

        pChatroom->Send( name, to, message );
    }


    virtual
 void Receive( string& from, string& message )
    {

        cout << from.c_str() << " to " << name.c_str() << " : [" << message.c_str() << "]" << endl;
    }
};


// More ConcreteColleague, omitted...

// "ConcreteMediator"
class Chatroom : public IChatroom
{

    // Fields
private:
    map<string, Participant*> mapParticipants;    // nickname to Participant map

    // Methods
public:
    void
 Register( Participant* pParticipant )
    {

        mapParticipants[ pParticipant->name ] = pParticipant;

        pParticipant->pChatroom = this;
    }


    void
 Send( string& from, string& to, string& message )
    {

        map<string, Participant*>::iterator ptr;
        ptr = mapParticipants.find(to);
        if
 ( ptr != mapParticipants.end() )
        {

            Participant* pto = (*ptr).second;
            pto->Receive( from, message );
        }
    }
};


int
 main()
{

    // Create chatroom
    Chatroom c;

    // Create 'chatters' and register them
    Participant George("George");
    Participant Paul("Paul");
    Participant Ringo("Ringo");

    c.Register( &George );
    c.Register( &Paul );
    c.Register( &Ringo );

    // Chatting participants
    George.Send( string("Paul"), string("Hi Paul!") );
    Paul.Send( string("Ringo"), string("Good Morning!") );
    Ringo.Send( string("George"), string("Hi Friend!") );

    return
 0;
}


2
、多線程Producer-Consumer:
以下是一個多線程Producer-Comsumer的例子,在該示例中,由於無法在多個Producer、Cosumer之間建立直接的聯繫,因此,通過Mediator類來完成這種信息交互,當Producer要Produce時,只需與Mediator進行交互,以查詢是否有空的Slot可供存放Product,而Comsumer要Comsume時,也只需與Mediator進行交互,以查詢是否有Product可供Comsume。
以下是該示例的Java實現:

import java.util.*;

class
 Product {
    int
 id;

    Product(int id) {
        this
.id = id;
    }
}


class
 Mediator {
    private
 boolean stopFlag = false;

    private
 Stack slot = new Stack();

    private
 int slotCount;

    public
 Mediator(int slotCount) {
        this
.slotCount = slotCount;
    }


    public
 boolean stop() {
        return
 stopFlag;
    }


    public
 void stop(boolean flag) {
        stopFlag = true;
    }


    public
 boolean put(Product product) {
        synchronized( slot ) {    // or synchronized on Mediator.class, but on slot is better and reasonable
            if ( slot.size() >= slotCount ) {
                return
 false;
            }


            slot.push( product );
        }


        return
 true;
    }


    public
 Product get() {
        synchronized( slot ) {
            if
 ( slot.empty() )
                return
 null;

            Product product = (Product)slot.pop();

            return
 product;
        }
    }
}


class
 Producer extends Thread {
    private
 Mediator med;

    private
 int id;

    private static
 int num = 1;

    public
 Producer(Mediator m) {
        med = m;
        id = num++;
    }


    public
 void run() {
        Product product;
        while
 ( !med.stop() ) {
            product = new Product((int) (Math.random() * 100));
            synchronized (System.out) {
                System.out.println("Producer[" + id + "] produces Product["
                    +
 product.id + "]");
            }

            while
 ( !med.stop() && !med.put(product) ) { // if put failed, try to put again and again.
                try {
                    sleep( 100 );
                }
 catch (InterruptedException ie) {
                }
            }


            try
 {
                sleep( 100 );
            }
 catch (InterruptedException ie) {
            }
        }
    }
}


class
 Consumer extends Thread {
    private
 Mediator med;

    private
 int id;

    private static
 int num = 1;

    public
 Consumer(Mediator m) {
        med = m;
        id = num++;
    }


    public
 void run() {
        Product product;
        while
 ( !med.stop() ) {
            product = med.get();
            if
 ( product != null ) {
                synchronized (System.out) {
                    System.out.println("Consumer[" + id + "] is consuming Product["
                        +
 product.id + "]");
                }
            }

            try
 {
                sleep( 100 );
            }
 catch (InterruptedException ie) {
            }
        }
    }
}


class
 MediatorDemo {
    public static
 void main(String[] args) {
        Mediator med = new Mediator(2);
        Thread thread[] = { new Producer(med), new Producer(med),
                new
 Consumer(med), new Consumer(med), new Consumer(med) };

        for
 (int i = 0; i < thread.length; i++)
            thread[i].start();

        // before stop all threads, sleep 1 second
        try {
            Thread.sleep(1000);
        }
 catch (InterruptedException ie) {
        }


        med.stop(true);
        // Wait for all threads to return
        try {
            for
 (int i = 0; i < thread.length; i++) {
                thread[i].join();
            }
        }
 catch (InterruptedException ie) {
        }
    }
}


由於JDK爲Java程序員提供了豐富的線程及線程同步支持,因此,要用Java解決上述問題,是比較簡單的,但是C++程序員就沒那麼幸運了,因爲C++到目前爲止並沒有規範線程支持(據說以後會有,但是按照C++標準n年更新一次的速度,誰知道什麼時候會有,好在我們不用等了,在一些關注應用的庫中已經能夠提供了一些十分穩定的Thread支持)。

下面是上述示例的等價C++實現,代碼中使用了Boost.Thread(關於如何在VC下使用boost::thread,見參考3;關於如何在VC6下編譯boost,見參考4,VC2003/VC2005的用戶請自行相應修改)。如果你不想使用boost,可以將其中的thread換成等價的其它線程庫實現中的等價類,如log4cxx::helpers::Thread、ACE_Thread/ACE_Thread_Manager,或者參考3中的跨平臺線程庫等,當然,MFC的CWinThread對於VC++開發人員應該是最簡便的選擇。
雖然使用了boost::thread,但由於缺少語言本身的支持(如果C++能像Java一樣提供synchronized關鍵字,實現會簡化很多),下面的實現仍然顯得有點臃腫,你在讀這個例子時如果對boost::thread的使用不感興趣,完全可以將它想象成一個普通的類,無需考慮具體的使用細節,只要明白其語義及整個代碼所表現的Mediator模式的實質即可。

#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition.hpp>
#include <boost/thread/xtime.hpp>

#include <iostream>
#include <time.h> // for time()

#include <Windows.h>    // for Sleep, change it for other platform

typedef
 boost::mutex::scoped_lock scoped_lock;
boost::mutex io_mutex;

class
 Product
{

    int
 num;
public
:
    Product(int num) : num(num) {}

    friend
 std::ostream& operator<< (std::ostream& os, Product& product)
    {

        return
 os << product.num;
    }
};


class
 Mediator
{

private
:
    boost::condition cond;
    boost::mutex mutex;

    Product** pSlot;    // product buffer/slot
    unsigned int slotCount,    // buffer size
        productCount; // current product count
    bool stopFlag;    // should all thread stop or not

public
:
    Mediator(const int slotCount) : slotCount(slotCount), stopFlag(false), productCount(0)
    {

        pSlot = new Product*[slotCount];
    }


    virtual
 ~Mediator()
    {

        for
 (int i = 0; i < static_cast<int>(productCount); i++)
        {

            delete
 pSlot[i];
        }

        delete
 [] pSlot;
    }


    bool
 Stop() const { return stopFlag; }
    void
 Stop(bool) { stopFlag = true; }

    void
 NotifyAll()
    {

        cond.notify_all();
    }


    bool
 Put( Product* pProduct)
    {

        scoped_lock lock(mutex);
        if
 (productCount == slotCount)
        {
            {

                scoped_lock lock(io_mutex);
                std::cout << "Buffer is full. Waiting..." << std::endl;
            }

            while
 (!stopFlag && (productCount == slotCount))
                cond.wait(lock);
        }

        if
 (stopFlag) // it may be notified by main thread to quit.
            return false;

        pSlot[ productCount++ ] = pProduct;
        cond.notify_one();    // this call may cause *pProduct to be changed if it wakes up a consumer

        return
 true;
    }


    bool
 Get(Product** ppProduct)
    {

        scoped_lock lock(mutex);
        if
 (productCount == 0)
        {
            {

                scoped_lock lock(io_mutex);
                std::cout << "Buffer is empty. Waiting..." << std::endl;
            }

            while
 (!stopFlag && (productCount == 0))
                cond.wait(lock);
        }

        if
 (stopFlag) // it may be notified by main thread to quit.
        {
            *
ppProduct = NULL;
            return
 false;
        }

        *
ppProduct = pSlot[--productCount];
        cond.notify_one();

        return
 true;
    }
};


class
 Producer
{

private
:
    Mediator* pMediator;
    static
 unsigned int num;
    unsigned int
 id;    // Producer id

public
:
    Producer(Mediator* pMediator) : pMediator(pMediator) { id = num++; }

    void
 operator() ()
    {

        Product* pProduct;
        srand( (unsigned)time( NULL ) + id );    // each thread need to srand differently
        while (!pMediator->Stop())
        {

            pProduct = new Product( rand() % 100 );
            // must print product info before call Put, as Put may wake up a consumer 
            // and cause *pProuct to be changed
            {
                scoped_lock lock(io_mutex);
                std::cout << "Producer[" << id << "] produces Product[" 
                    << *
pProduct << "]" << std::endl;
            }

            if
 (!pMediator->Put(pProduct))    // this function only fails when it is notified by main thread to exit
                delete pProduct;

            Sleep(100);
        }
    }
};


unsigned int
 Producer::num = 1;

class
 Consumer
{

private
:
    Mediator* pMediator;
    static
 unsigned int num;
    unsigned int
 id;    // Consumer id

public
:
    Consumer(Mediator* pMediator) : pMediator(pMediator) { id = num++; }

    void
 operator() ()
    {

        Product* pProduct = NULL;
        while
 (!pMediator->Stop())
        {

            if
 (pMediator->Get(&pProduct))
            {

                scoped_lock lock(io_mutex);
                std::cout << "Consumer[" << id << "] is consuming Product[" 
                    << *
pProduct << "]" << std::endl;
                delete
 pProduct;
            }


            Sleep(100);
        }
    }
};


unsigned int
 Consumer::num = 1;

int
 main()
{

    Mediator mediator(2);    // we have only 2 slot to put products

    // we have 2 producers
    Producer producer1(&mediator);
    boost::thread thrd1(producer1);
    Producer producer2(&mediator);
    boost::thread thrd2(producer2);
    // and we have 3 consumers
    Consumer consumer1(&mediator);
    boost::thread thrd3(consumer1);
    Consumer consumer2(&mediator);
    boost::thread thrd4(consumer2);
    Consumer consumer3(&mediator);
    boost::thread thrd5(consumer3);

    // wait 1 second
    Sleep(1000);
    // and then try to stop all threads
    mediator.Stop(true);
    mediator.NotifyAll();

    // wait for all threads to exit
    thrd1.join();
    thrd2.join();
    thrd3.join();
    thrd4.join();
    thrd5.join();

    return
 0;
}


參考:
1
、http://www.dofactory.com/Patterns/PatternMediator.aspx
2、http://home.earthlink.net/~huston2/dp/MediatorDemosJava
3、http://blog.vckbase.com/billdavid/archive/2005/03/07/3412.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章