1. 程式人生 > >【設計模式】 模式PK:觀察者模式VS責任鏈模式

【設計模式】 模式PK:觀察者模式VS責任鏈模式

ipa 隨機 保留 聲明 pri 測試 void c_str window

1、概述

為什麽要把觀察者模式和責任鏈模式放在一起對比呢?看起來這兩個模式沒有太多的相似性,真沒有嗎?回答是有。我們在觀察者模式中也提到了觸發鏈(也叫做觀察者鏈)的問題,一個具體的角色既可以是觀察者,也可以是被觀察者,這樣就形成了一個觀察者鏈。這與責任鏈模式非常類似,它們都實現了事務的鏈條化處理,比如說在上課的時候你睡著了,打鼾聲音太大,蓋過了老師講課聲音,老師火了,捅到了校長這裏,校長也處理不了,然後告狀給你父母,於是你的魔鬼日子來臨了,這是責任鏈模式,老師、校長、父母都是鏈中的一個具體角色,事件(你睡覺)在鏈中傳遞,最終由一個具體的節點來處理,並將結果反饋給調用者(你挨揍了)。那什麽是觸發鏈?你還是在課堂上睡覺,還是打鼾聲音太大,老師火了,但是老師掏出個擴音器來講課,於是你睡不著了,同時其他同學的耳朵遭殃了,這就是觸發鏈,其中老師既是觀察者(相對你)也是被觀察者(相對其他同學),事件從“你睡覺”到老師這裏轉化為“擴音器放大聲音”,這也是一個鏈條結構,但是鏈結構中傳遞的事件改變了。

我們還是以一個具體的例子來說明兩者的區別,DNS協議相信大家都聽說過,只要在“網絡設置”中設置一個DNS服務器地址就可以把我們需要的域名翻譯成IP地址。DNS協議還是比較簡單的,傳遞過去一個域名以及記錄標誌(比如是要A記錄還是要MX記錄),DNS就開始查找自己的記錄樹,找到後把IP地址反饋給請求者。我們可以在Windows操作系統中了解一下DNS解析過程,在DOS窗口下輸入nslookup命令後,結果如圖所示。

技術分享

我們的意圖就是要DNS服務器192.168.10.1解析出www.xxx.com.cn的IP地址,DNS服務器是如何工作的呢?圖中的192.168.10.1這個DNSServer存儲著全球的域名和IP之間的對應關系嗎?不可能,目前全球的域名數量是1.7億個,如此龐大的數字,每個DNS服務器都存儲一份,還怎麽快速響應?DNS解析的響應時間一般都是毫秒級別的,如此高的性能要求還怎麽讓DNS服務器遍地開花呢?而且域名變更非常頻繁,數據讀寫的量也非常大,不可能每個DNS服務器都保留這1.7億數據,那麽是怎麽設計的呢?DNS協議還是很聰明的,它規定了每個區域的DNS服務器(LocalDNS)只保留自己區域的域名解析,對於不能解析的域名,則提交上級域名解析器解析,最終由一臺位於美國洛杉磯的頂級域名服務器進行解析,返回結果。很明顯這是一個事務的鏈結構處理,我們使用兩種模式來實現該解析過程。

2、責任鏈模式實現DNS解析過程

2.1 類圖

本小節我們用責任鏈模式來實現DNS解析過程。首先我們定義一下業務場景,這裏有三個DNS服務器:上海DNS服務器(區域服務器)、中國頂級DNS服務器(父服務器)、全球頂級DNS服務器:

技術分享

假設有請求者發出請求,由上海DNS進行解析,如果能夠解析,則返回結果,若不能解析,則提交給父服務器(中國頂級DNS)進行解析,若還不能解析,則提交到全球頂級DNS進行解析,若還不能解析呢?那就返回該域名無法解析。確實,這與責任鏈模式非常相似,我們把這一過程抽象一下。

技術分享

我們來解釋一下類圖,Recorder是一個BO對象,它記錄DNS服務器解析後的結果,包括域名、IP地址、屬主(即由誰解析的),除此之外還有getter/setter方法。DnsServer抽象類中的resolve方法是一個基本方法,每個DNS服務器都必須擁有該方法,它對DNS進行解析,如何解析呢?具體是由echo方法來實現的,每個DNS服務器獨自實現。類圖還是比較簡單的。

2.2 代碼

2.2.1 解析記錄

class CDnsServer
{
public:
    CDnsServer(){};
    ~CDnsServer(){};

    //解析域名
    CRecorder *mopResolve(const string &sDomain)
    {
        CRecorder *op_recorder = NULL; 
        
        //是本服務器能解析的域名
        if (mbIsLocal(sDomain))
        {
            op_recorder = mopEcho(sDomain);
        }
        else
        {
            //提交上級DNS進行解析
            op_recorder = mopUpServer->mopResolve(sDomain);
        }
        
        return op_recorder;
    }

    // 指向上級DNS
    void mvSetUperServer(CDnsServer *opUperServer) { mopUpServer = opUperServer; }

protected:
    // 每個DNS都有一個數據處理區( ZONE), 檢查域名是否在本區中
    virtual bool mbIsLocal(const string &sDomain) = 0;

    //每個DNS服務器都必須實現解析任務
    virtual CRecorder *mopEcho(const string &sDomain)
    {
        CRecorder *op_recorder = new CRecorder();

        // 獲得IP地址
        op_recorder->mvSetIP(msGetIPAddress());
        op_recorder->mvSetDomain(sDomain);

        return op_recorder;
    }

private:
    // 隨機產生一個IP地址, 工具類
    string msGetIPAddress()
    {
        return "假裝這個是IP~";
    }

private:
    CDnsServer *mopUpServer;  //上級DNS是誰
};

該類中有一個方法——msGetIPAddress方法——沒有在類圖中展現出來,它用於實現隨機生成IP地址,這是我們為模擬DNS解析場景而建立的一個虛擬方法,在實際的應用中是不可能出現的。

2.2.2 上海的DNS服務器

象DNS服務器編寫完成,我們再來看具體的DNS服務器,先看上海的DNS服務器。

class CSHDnsServer : public CDnsServer
{
public:
    CSHDnsServer() {};
    ~CSHDnsServer(){};

protected:
    CRecorder *mopEcho(const string &sDomain)
    {
        CRecorder *op_recorder = CDnsServer::mopEcho(sDomain);
        op_recorder->mvSetOwner("上海DNS服務器");

        return op_recorder;
    }

    // 定義上海的DNS服務器能處理的級別
    bool mbIsLocal(const string &sDomain)
    {
        string::size_type i_index = sDomain.find(".sh.cn");
        return i_index == string::npos ? false : true;
    }

};

為什麽要覆寫echo方法?各具體的DNS服務器實現自己的解析過程,屬於個性化處理,它代表的是每個DNS服務器的不同處理邏輯。還要註意一下,我們在這裏做了一個簡化處理,所有以".sh.cn"結尾的域名都由上海DNS服務器解析。其他的中國頂級DNS和全球頂級DNS實現過程類似。

2.2.3 中國和全球DNS

//中國頂級DNS服務器
class CChinaTopDnsServer : public CDnsServer
{
public:
    CChinaTopDnsServer() {};
    ~CChinaTopDnsServer(){};

protected:
    CRecorder *mopEcho(const string &sDomain)
    {
        CRecorder *op_recorder = CDnsServer::mopEcho(sDomain);
        op_recorder->mvSetOwner("中國頂級DNS服務器");

        return op_recorder;
    }

    // 定義中國頂級DNS服務器能處理的級別
    bool mbIsLocal(const string &sDomain)
    {
        string::size_type i_index = sDomain.find(".cn");
        return i_index == string::npos ? false : true;
    }
};

//全球頂級DNS服務器
class CTopDnsServer : public CDnsServer
{
public:
    CTopDnsServer() {};
    ~CTopDnsServer(){};

protected:
    CRecorder *mopEcho(const string &sDomain)
    {
        CRecorder *op_recorder = CDnsServer::mopEcho(sDomain);
        op_recorder->mvSetOwner("全球頂級DNS服務器");

        return op_recorder;
    }

    // 定義中國頂級DNS服務器能處理的級別
    bool mbIsLocal(const string &sDomain)
    {
        //所有的域名最終的解析地點
        return  true;
    }
};

2.2.4 場景調用

所有的DNS服務器都準備好了,下面我們寫一個客戶端來模擬一下IP地址是怎麽解析的。

int main()
{
    //上海域名服務器
    CSHDnsServer *op_sh_server = new CSHDnsServer;
    //中國頂級域名服務器
    CChinaTopDnsServer *op_cn_server = new CChinaTopDnsServer;
    //全球頂級域名服務器
    CTopDnsServer *op_top_server = new CTopDnsServer;

    //定義查詢路徑
    op_sh_server->mvSetUperServer(op_cn_server);
    op_cn_server->mvSetUperServer(op_top_server);

    // 解析域名
    cout << "----------解析域名----------" << endl;
    CRecorder *op_recorder =  op_sh_server->mopResolve("www.xxx.com.cn");
    cout << op_recorder->msGetInfo().c_str() << endl;

    op_recorder = op_sh_server->mopResolve("www.xxx.com.sh.cn");
    cout << op_recorder->msGetInfo().c_str() << endl;

    op_recorder = op_sh_server->mopResolve("www.xxx.com.com");
    cout << op_recorder->msGetInfo().c_str() << endl;

    return 0;
}

2.2.5 執行結果

技術分享

請註意看運行結果,以".sh.cn"結尾的域名確實由上海DNS服務器解析了,以".cn"結尾的域名由中國頂級DNS服務器解析了,其他域名都由全球頂級DNS服務器解析。這個模擬過程看起來很完整,它完全就是責任鏈模式的一個具體應用,把一個請求放置到鏈中的首節點,然後由鏈中的某個節點進行解析並將結果反饋給調用者。但是,這個解析過程是有缺陷的,什麽缺陷?看完觀察者模式的解析就明白了。

3、觀察者模式實現DNS解析過程

3.1 類圖

上面說到使用責任鏈模式模擬DNS解析過程是有缺陷的,究竟有什麽缺陷?大家是不是覺得這個解析過程很完美了,沒什麽問題了?我們來做一個實驗,在dos窗口下輸入nslookup命令,然後輸入多個域名,註意觀察返回值有哪些數據是相同的。可以看出,解析者都相同,都是由同一個DNS服務器解析的,準確地說都是由本機配置的DNS服務器做的解析。這與我們上面的模擬過程是不相同的,看看我們模擬的過程,對請求者來說,".sh.cn"是由區域DNS解析的,".com"卻是由全球頂級DNS解析的,與真實的過程不相同,這是怎麽回事呢?

肯定地說,采用責任鏈模式模擬DNS解析過程是不完美的,或者說是有缺陷的,怎麽來修復這個缺陷呢?我們先來看看真實的DNS解析過程。

技術分享

解析一個域名的完整路徑如圖中的標號①~⑥所示,首先由請求者發送一個請求,然後由上海DNS服務器嘗試解析,若不能解析再通過路徑②轉發給中國頂級DNS進行解析,解析後的結果通過路徑⑤返回給上海DNS服務器,然後由上海DNS服務器通過路徑⑥返回給請求者。同樣,若中國頂級DNS不能解析,則通過路徑③轉由全球頂級DNS進行解析,通過路徑④把結果返回給中國頂級DNS,然後再通過路徑⑤返回給上海DNS。註意看標號⑥,不管一個域名最終由誰解析,最終反饋到請求者的還是第一個節點,也就是說首節點負責對請求者應答,其他節點都不與請求者交互,而只與自己的左右節點交互。實際上我們的DNS服務器確實是如此處理的,例如本機請求查詢一個www.abcdefg.com的域名,上海DNS服務器解析不到這個域名,於是提交到中國頂級DNS服務器,如果中國頂級DNS服務器有該域名的記錄,則找到該記錄,反饋到上海DNS服務器,上海DNS服務器做兩件事務處理:一是響應請求者,二是存儲該記錄,以備其他請求者再次查詢,這類似於數據緩存。

整個場景我們已經清晰,想想看,我們把請求者看成是被觀察者,它的行為或屬性變更通知了觀察者——上海DNS,上海DNS又作為被觀察者出現了自己不能處理的行為(行為改變),通知了中國頂級DNS,依次類推,這是不是一個非常標準的觸發鏈?而且還必須是同步的觸發,異步觸發已經在該場景中失去了意義(讀者可以想想為什麽)。分析了這麽多,我們用觸發鏈來模擬DNS的解析過程。

技術分享

● 標示聲明

表示所有的DNS服務器都具備雙重身份:既是觀察者也是被觀察者,這很重要,它聲明所有的服務器都具有相同的身份標誌,具有該標誌後就可以在鏈中隨意移動,而無需固定在鏈中的某個位置(這也是鏈的一個重要特性)。

● 業務抽象

方法setUpperServer的作用是設置父DNS,也就是設置自己的觀察者,update方法不僅僅是一個事件的處理者,也同時是事件的觸發者。

3.2 代碼

我們來看代碼,首先是最簡單的,Recorder類與責任鏈模式中的記錄相同,這裏不再贅述。那我們就先看看該模式的核心抽象DnsServer。

3.2.1 抽象DNS服務器

class CDnsServer
{
public:
    CDnsServer(){};
    ~CDnsServer(){};

    //處理請求, 也就是接收到事件後的處理
    void mvUpdate(CRecorder *opRecorder)
    {
        if (mbIsLocal(opRecorder))
        {
            //如果本機能解析
            opRecorder->mvSetIP(msGetIPAddress());
        }
        else
        {
            //本機不能解析, 則提交到上級DNS
            mopUpServer->mvUpdate(opRecorder);
        }
        // 簽名
        mvSign(opRecorder);
    }

    void mvSetUpServer(CDnsServer *opDnsServer) { mopUpServer = opDnsServer; };
protected:
    // 每個DNS服務器簽上自己的名字
    virtual void mvSign(CRecorder *opRecorder) = 0;

    //每個DNS服務器都必須定義自己的處理級別
    virtual bool mbIsLocal(CRecorder *opRecorder) = 0;

private:
    // 隨機產生一個IP地址, 工具類
    string msGetIPAddress()
    {
        return "假裝這個是IP~";
    }

private:
    // 上級解析服務器
    CDnsServer *mopUpServer;
};

mvSign方法是簽名,這個記錄是由誰解析出來的,就由各個實現類獨自來實現。

3.2.2 DNS Server

三個DnsServer的實現類都比較簡單。

//上海DNS服務器
class CSHDnsServer : public CDnsServer
{
public:
    CSHDnsServer(){};
    ~CSHDnsServer(){};

    void mvSign(CRecorder *opRecorder)
    {
        opRecorder->mvSetOwner("上海DNS服務器");
    }

    // 定義上海的DNS服務器能處理的級別
    bool mbIsLocal(CRecorder *opRecorder)
    {
        string::size_type i_index = opRecorder->msGetDomain().find(".sh.cn");
        return i_index == string::npos ? false : true;
    }
};

//中國頂級DNS服務器
class CChinaTopDnsServer : public CDnsServer
{
public:
    CChinaTopDnsServer(){};
    ~CChinaTopDnsServer(){};

    void mvSign(CRecorder *opRecorder)
    {
        opRecorder->mvSetOwner("中國頂級DNS服務器");
    }

    bool mbIsLocal(CRecorder *opRecorder)
    {
        string::size_type i_index = opRecorder->msGetDomain().find(".cn");
        return i_index == string::npos ? false : true;
    }
};

//全球頂級DNS服務器
class CTopDnsServer : public CDnsServer
{
public:
    CTopDnsServer(){};
    ~CTopDnsServer(){};

    void mvSign(CRecorder *opRecorder)
    {
        opRecorder->mvSetOwner("全球頂級DNS服務器");
    }

    bool mbIsLocal(CRecorder *opRecorder)
    {
        //所有的域名最終的解析地點
        return true;
    }
};

3.2.3 場景調用

我們再建立一個場景類模擬一下DNS解析過程。

int main()
{
    //上海域名服務器
    CSHDnsServer *op_sh_server = new CSHDnsServer;
    //中國頂級域名服務器
    CChinaTopDnsServer *op_cn_server = new CChinaTopDnsServer;
    //全球頂級域名服務器
    CTopDnsServer *op_top_server = new CTopDnsServer;

    //定義查詢路徑
    op_sh_server->mvSetUpServer(op_cn_server);
    op_cn_server->mvSetUpServer(op_top_server);

    CRecorder *op_recorder = new CRecorder;

    // 解析域名
    cout << "----------解析域名----------" << endl;
    op_recorder->mvSetDomain("www.xxx.com.cn");
    op_sh_server->mvUpdate(op_recorder);
    cout << op_recorder->msGetInfo().c_str() << endl;

    op_recorder->mvSetDomain("www.xxx.com.sh.cn");
    op_sh_server->mvUpdate(op_recorder);
    cout << op_recorder->msGetInfo().c_str() << endl;

    op_recorder->mvSetDomain("www.xxx.com.com");
    op_sh_server->mvUpdate(op_recorder);
    cout << op_recorder->msGetInfo().c_str() << endl;

    return 0;
}

與責任鏈模式中的場景類很相似。請註意op_sh_server->mvUpdate(op_recorder)這句代碼,這是我們虛擬了觀察者觸發動作,完整的做法是把場景類作為一個被觀察者,然後設置觀察者為上海DNS服務器,再進行測試,其結果完全相同,我們這裏為減少代碼量采用了簡化處理。

3.2.4 執行結果

技術分享

可以看出,所有的解析結果都是由上海DNS服務器返回的,這才是真正的DNS解析過程。如何知道它是由上海DNS服務器解析的還是由別的DNS服務器解析的呢?很好辦,把代碼拷貝過去,然後調試跟蹤一下就可以了。或者仔細看看代碼,理解一下代碼邏輯也可以非常清楚地知道它是如何解析的。

3.3 小結

再仔細看一下我們的代碼邏輯,上下兩個節點之間的關系很微妙,很有意思。

● 下級節點對上級節點頂禮膜拜

比如我們輸入的這個域名www.xxx.com,上海域名服務器只知道它是由父節點(中國頂級DNS服務器)解析的,而不知道父節點把該請求轉發給了更上層節點(全球頂級DNS服務器),也就是說下級節點關註的是上級節點的響應,只要是上級反饋的結果就認為是上級的。www.xxx.com這個域名最終是由最高節點(全球頂級DNS服務器)解析的,它把解析結果傳遞給第二個節點(中國頂級DNS服務器)時的簽名為“全球頂級DNS服務器”,而第二個節點把請求傳遞給首節點(上海DNS服務器)時的簽名被修改為“中國頂級DNS服務器”。所有從上級節點反饋的響應都認為是上級節點處理的結果,而不追究到底是不是真的是上級節點處理的。

● 上級節點對下級節點絕對信任

上級節點只對下級節點負責,它不關心下級節點的請求從何而來,只要是下級發送的請求就認為是下級的。還是以www.xxx.com域名為例,當最高節點(全球頂級DNS服務器)獲得解析請求時,它認為這個請求是誰的?當然是第二個節點(中國頂級DNS服務器)的,否則它也不會把結果反饋給它,但是這個請求的源頭卻是首節點(上海DNS服務器)的。

4、總結

通過對DNS解析過程的實現,我們發現觸發鏈和責任鏈雖然都是鏈結構,但是還是有區別的。

● 鏈中的消息對象不同

從首節點開始到最終的尾節點,兩個鏈中傳遞的消息對象是不同的。責任鏈模式基本上不改變消息對象的結構,雖然每個節點都可以參與消費(一般是不參與消費),類似於“雁過拔毛”,但是它的結構不會改變,比如從首節點傳遞進來一個String對象或者Person對象,不會到鏈尾的時候成了int對象或者Human對象,這在責任鏈模式中是不可能的,但是在觸發鏈模式中是允許的,鏈中傳遞的對象可以自由變化,只要上下級節點對傳遞對象了解即可,它不要求鏈中的消息對象不變化,它只要求鏈中相鄰兩個節點的消息對象固定。

● 上下節點的關系不同

在責任鏈模式中,上下節點沒有關系,都是接收同樣的對象,所有傳遞的對象都是從鏈首傳遞過來,上一節點是什麽沒有關系,只要按照自己的邏輯處理就成。而觸發鏈模式就不同了,它的上下級關系很親密,下級對上級頂禮膜拜,上級對下級絕對信任,鏈中的任意兩個相鄰節點都是一個牢固的獨立團體。

● 消息的分銷渠道不同

在責任鏈模式中,一個消息從鏈首傳遞進來後,就開始沿著鏈條向鏈尾運動,方向是單一的、固定的;而觸發鏈模式則不同,由於它采用的是觀察者模式,所以有非常大的靈活性,一個消息傳遞到鏈首後,具體怎麽傳遞是不固定的,可以以廣播方式傳遞,也可以以跳躍方式傳遞,這取決於處理消息的邏輯。

【設計模式】 模式PK:觀察者模式VS責任鏈模式