2009-09-29 49 views
7

這是一個通用的C++設計問題。如何在C++中透明地處理不同的協議版本?

我在寫一個使用客戶端/服務器模型的應用程序。現在我正在寫服務器端。許多客戶已經存在(有些是由我自己寫的,有些是由第三方寫的)。問題是這些現有的客戶端都使用不同的協議版本(多年來已經有2-3次協議更改)。

由於我正在重新編寫服務器,我認爲這將是設計我的代碼的好時機,以便我可以透明地處理許多不同的協議版本。在所有協議版本中,來自客戶端的第一個通信包含協議版本,因此對於每個客戶端連接,服務器確切知道需要談論哪個協議。

天真的方法做,這是垃圾,像這樣的語句代碼:

if (clientProtocolVersion == 1) 
    // do something here 
else if (clientProtocolVersion == 2) 
    // do something else here 
else if (clientProtocolVersion == 3) 
    // do a third thing here... 

該解決方案是相當差,有以下原因:

  1. 當我添加新的協議版本,我必須在源碼樹中的任何地方找到這些if語句,並修改它們以添加新功能。
  2. 如果出現一個新的協議版本,並且協議版本的某些部分與另一個版本相同,我需要修改if語句,以便他們讀取if (clientProtoVersion == 5 || clientProtoVersion == 6)
  3. 我相信有更多的原因是它的設計不好,但現在我想不起來。

我在尋找的是一種使用C++語言的特性智能處理不同協議的方法。我想過用模板類,可能與模板參數指定的協議版本,或者一個類層次結構,對於每一個不同的協議版本一類...

我敢肯定,這是一個很常見的設計模式,很多很多人以前一定有過這個問題。

編輯:

你們中許多人認爲繼承層次結構,在頂部的最古老的協議版本,像這樣的(請原諒我的ASCII藝術):

IProtocol 
    ^
    | 
CProtoVersion1 
    ^
    | 
CProtoVersion2 
    ^
    | 
CProtoVersion3 

...就resuse而言,這似乎是一個明智的做法。但是,當您需要擴展協議並添加基本上新的消息類型時會發生什麼?如果我在IProtocol中添加虛擬方法,並在CProtocolVersion4中實現這些新方法,那麼在早期協議版本中如何處理這些新方法?我想我的選擇是:

  • 使默認實現NO_OP(或可能在某處記錄消息)。
  • 拋出一個異常,雖然這似乎是一個壞主意,即使我輸入它。
  • ...做別的事嗎?

EDIT2:

而且上述問題,會發生什麼,當一個新的協議消息需要比舊版本更多的投入?例如:

在通訊協定第1版,我可能有:

ByteArray getFooMessage(string param1, int param2)

而且在協議版本2我可能想:

ByteArray getFooMessage(string param1, int param2, float param3)

兩個不同的協議版本現在有不同的方法簽名,這很好,除了它迫使我通過所有調用代碼並將所有調用改爲2個參數爲3個參數,具體取決於正在使用的協議版本,我這是我試圖避免的第一個地方!

從其他代碼中分離協議版本信息的最佳方式是什麼,以便當前協議的細節對您是隱藏的?

回答

10

由於您需要動態選擇要使用的協議,因此使用不同的類(而不是模板參數)來選擇協議版本似乎是正確的方法。本質上這是戰略模式,但如果你想真正詳細說明,訪問者也是可能的。

因爲這些都是同一協議的不同版本,所以你可能在基類中有共同的東西,然後是子類中的差異。另一種方法可能是讓基類用於最早版本的協議,然後讓每個後續版本都有一個從前一版本繼承而來的類。這是一個有點不尋常的繼承樹,但它的好處在於它可以保證爲更高版本所做的更改不會影響舊版本。 (我假設老版本協議的類會很快穩定下來,然後很少發生變化

但是,如果您決定組織層次結構,那麼您希望儘快選擇協議版本對象知道協議版本,然後把它傳遞給你需要「談論」協議的各種事情。

+2

實際上,策略模式是最有可能的方法,您可能希望與Factory合併,該工廠將負責爲您使用的版本提供正確的「策略」。 – 2009-09-29 16:23:33

0

我傾向於使用不同的類來實現不同協議的適配器到相同的接口。

根據協議和差異,使用TMP獲取狀態機或協議詳細信息的一些好處,但生成六組任何使用六個協議版本的代碼可能是不值得的;運行時多態性已足夠,並且對於大多數情況下,TCP IO可能足夠慢而不想硬編碼所有內容。

0

可能過於簡化了,但這聽起來像是一個繼承工作嗎? 一個基類IProtocol,它定義協議的作用(可能還有一些常用的方法),然後針對每個協議使用IProtocol的一個實現?

0

你需要一個協議,工廠,返回一個協議處理程序appropraite版本:

ProtocolHandler& handler = ProtocolFactory::getProtocolHandler(clientProtocolVersion); 

然後,您可以使用繼承來只更新已更改版本之間的協議的部分。

請注意,ProtocolHandler(基類)基本上是一個有瑕疵的模式。因此,它不應該保持自己的狀態(如果需要通過方法傳入並且策略會更新狀態對象的屬性)。

因爲stratergy不保持狀態,我們可以在任意數量的線程之間共享ProtcolHandler,因此所有權不需要離開工廠對象。因此,工廠只需要爲其理解的每個協議版本創建一個處理程序對象(這甚至可以懶散地完成)。由於工廠對象保留所有權,您可以返回協議處理程序的參考。

4

我已經使用(並聽說過其他人使用)模板來解決這個問題。這個想法是,你將不同的協議分解成基本的原子操作,然後使用諸如boost::fusion::vector之類的東西來構建各個塊之外的協議。

下面是一個極其粗糙(手件漏掉的),例如:

// These are the kind of atomic operations that we can do: 
struct read_string { /* ... */ }; 
struct write_string { /* ... */ }; 
struct read_int { /* ... */ }; 
struct write_int { /* ... */ }; 

// These are the different protocol versions 
typedef vector<read_string, write_int> Protocol1; 
typedef vector<read_int, read_string, write_int> Protocol2; 
typedef vector<read_int, write_int, read_string, write_int> Protocol3; 

// This type *does* the work for a given atomic operation 
struct DoAtomicOp { 
    void operator()(read_string & rs) const { ... } 
    void operator()(write_string & ws) const { ... } 
    void operator()(read_int & ri) const { ... } 
    void operator()(write_int & wi) const { ... } 
}; 

template <typename Protocol> void doProtWork (...) { 
    Protocl prot; 
    for_each (prot, DoAtomicOp (...)); 
} 

因爲通訊協定版本是動態的,你需要一個頂級switch語句來確定哪些通訊協定使用。

void doWork (int protocol ...) { 
    switch (protocol) { 
    case PROTOCOL_1: 
    doProtWork<Protocol1> (...); 
    break; 
    case PROTOCOL_2: 
    doProtWork<Protocol2> (...); 
    break; 
    case PROTOCOL_3: 
    doProtWork<Protocol3> (...); 
    break; 
    }; 
} 

要添加一個新的協議(即利用現有的類型),你只需要做定義通訊協定順序:

typedef vector<read_int, write_int, write_int, write_int> Protocol4; 

,然後添加一個新條目switch語句。

+0

這是一個非常有趣的方法,謝謝! – Thomi 2009-09-30 07:20:57

0

我會同意Pete Kirkham的看法,我認爲維持可能的6個不同版本的類以支持不同的協議版本是相當不愉快的。如果你可以做到這一點,似乎最好讓舊版本實現適配器轉換到最新的協議,所以你只有一個工作協議來維護。你仍然可以使用如上所示的繼承層次結構,但較舊的協議實現只是做一個適配,然後調用最新的協議。