MemCache++ 是一個輕量級、型別安全、易於使用且功能齊全的Memcache客戶端。
它由Dean Michael Berris開發,他是一位C++狂熱愛好者,喜歡從事網路庫(cpp-netlib.github.com)的工作,目前在Google澳大利亞工作。他也是Google派往ISO C++委員會的代表之一。您可以在deanberris.github.com上閱讀他發表的作品,並在www.cplusplus-soup.com上閱讀他的C++部落格。
建議學習設計良好的庫來掌握C++概念,本文的目標是探討memcache++的一些設計選擇,使其易於理解和使用。
讓我們透過CppDepend來分析memcache++的設計。
名稱空間模組化
名稱空間是模組化應用程式的良好解決方案,但不幸的是,C++專案中使用得不夠充分,隨便看看開源C++專案就能發現這一事實。更重要的是,當我們搜尋C++名稱空間定義時,常見的定義是這樣的:
A namespace defines a new scope. They provide a way to avoid name collisions.
許多時候,命名衝突被視為首要動機,而不是模組化,這與C#和Java不同,在後兩者中,名稱空間在模組化應用程式方面被引用得更多。
然而,一些現代C++庫,如boost,使用名稱空間來很好地組織庫並鼓勵開發人員使用它們。
memcache++的名稱空間模組化如何?
這是memcache++名稱空間之間的依賴關係圖。
名稱空間有兩個主要原因:
- 模組化庫。
- 隱藏細節,如“memcache::detail”名稱空間。如果我們想告知庫使用者他們不需要直接使用該名稱空間內的型別,這種方法會非常有趣。對於C#,“internal”關鍵字完成了這項工作,但在C++中,無法向庫使用者隱藏公共型別。
memcache++優雅地利用了名稱空間概念,然而,memcache和memcache::detail之間存在依賴迴圈。我們可以透過在memcache中查詢memcache::detail使用的型別來移除這個依賴迴圈。
為此,我們可以執行以下CQlinq請求:
from t in Types where t.IsUsedBy("memcache.detail")
&& t.ParentNamespace.Name=="memcache"
select new { t,t.TypesUsingMe }
執行請求後的結果如下:
為了移除依賴迴圈,我們可以將pool_directive和server_pool_test移到memcache中。
泛型還是面向物件?
在C++世界中,有兩個非常流行的學派:面向物件程式設計和泛型程式設計,每種方法都有其擁護者。這篇
文章解釋了它們之間的張力。
memcache++使用了什麼方法?
為了回答這個問題,讓我們先搜尋泛型型別。
from t in Types where t.IsGeneric && !t.IsThirdParty select t
那些非泛型的型別呢?
from t in Types where !t.IsGeneric && !t.IsGlobal && !t.IsNested
&& !t.IsEnumeration && t.ParentProject.Name=="memcache"
select t
幾乎所有非泛型型別都與異常類有關,為了更好地瞭解它們的用途比例,樹狀圖檢視非常有用。
藍色矩形代表CQLinq查詢的結果,正如我們所見,只有一小部分庫與非泛型型別有關。
最後,我們可以搜尋泛型方法。
from m in Methods where m.IsGeneric && !m.IsThirdParty select m
正如我們所觀察到的,memcache++主要使用泛型,但這不足以確認它遵循C++泛型方法。要檢查這一點,一個好的指標是使用繼承和動態多型性。確實,面向物件程式設計主要使用它們,然而,對於泛型方法,繼承的使用非常有限,並且會避免動態多型性。
讓我們搜尋具有基類的型別。
from t in Types where t.BaseClasses.Count()>0 && !t.IsThirdParty
&& t.ParentProject.Name=="memcache"
select t
異常類使用繼承是很正常的,但其他類呢?它們是否使用繼承來實現動態多型性?為了回答這個問題,讓我們搜尋所有虛方法。
from m in Methods where m.IsVirtual select m
只有異常類有一個虛方法,對於使用繼承的其他少數類,主要動機是重用基類的程式碼。
如果未使用動態多型性,那麼在需要為特定類實現其他行為時,會採用什麼解決方案?
泛型方法常用的解決方案是使用策略。以下是維基百科的簡要定義:
“策略導向設計中的核心慣用法是一個類模板(稱為宿主類),它接受多個型別引數作為輸入,這些型別引數透過使用者選擇的型別(稱為策略類)進行例項化,每個型別都實現一個特定的隱式介面(稱為策略)。”
memcache++在memcache.policies名稱空間中有許多策略。
讓我們來看一個memcache++的例子,以便更好地理解基於策略的設計。
memcache++使用basic_handle型別來實現所有命令,如add、set、get和delete快取。該類定義如下:
template <
class threading_policy = policies::default_threading,
class data_interchange_policy = policies::binary_interchange,
class hash_policy = policies::default_hash
>
struct basic_handle
memcache++是執行緒安全的,並且在多執行緒環境中需要管理同步。預設情況下,threading_policy是“default_threading”,不需要特殊的處理。然而,對於多執行緒,使用的策略是“boost_threading”。
讓我們看一下connect方法的實現。
void connect(boost::uint64_t timeout = MEMCACHE_TIMEOUT) {
typename threading_policy::lock scoped_lock(*this);
for_each(servers.begin(), servers.end(), connect_impl(service_, timeout));
};
如果threading_policy是“default_threading”,第一行就沒有影響,因為lock建構函式什麼也沒做。然而,如果是boost_threading,lock則使用boost線上程之間進行同步。
使用策略為我們提供了更多的靈活性來實現不同的行為,而且理解和使用它們並不困難。
泛型仿函式
memcache++實現了許多與快取互動的命令,如add、get、set和delete。在這種情況下,命令模式是一個不錯的選擇。
memcache++透過使用泛型仿函式來實現此模式。以下是獲取所有仿函式的CQLinq查詢:
from t in Types where t.Methods.Where(a=>a.IsOperator
&& a.Name.Contains("()")).Count()>0
select t
仿函式封裝了一個函式呼叫及其狀態,可以用於延遲呼叫,並充當回撥。泛型仿函式比普通仿函式提供了更多的靈活性。
介面暴露
庫如何暴露其功能非常重要,它會影響其靈活性和易用性。
為了瞭解這一點,讓我們搜尋測試專案與memcache++庫之間的通訊。
from m in Methods where m.IsUsedBy ("test")
select m
測試專案主要使用泛型方法來呼叫memcache++的功能。使用模板方法的優點是什麼?為什麼不使用類或函式?
對於面向物件方法,庫介面由類和函式組成,對於設計良好的介面,抽象類被用作契約來強制低耦合。這個解決方案非常有趣,但有一些缺點:
- 介面變得更加複雜,並且可能經常更改:為了解釋這一點,讓我們以memcache++暴露的add方法為例。如果我們不使用泛型方法,則必須新增許多方法,每種方法都針對特定型別int、double、string……
然而,泛型add方法宣告為add<T>,其中T是型別。在這種情況下,我們只需要一個方法,即使我們想新增另一種型別,介面也不需要改變。
- 介面的靈活性較低:例如,如果我們暴露一個這樣的方法
calculate(IAlgo* algo)。
使用者必須提供一個繼承自IAlgo的類。然而,如果我們使用泛型並將其定義為calculate<T>,使用者只需要提供一個具有所需方法的類,而不必繼承自IAlgo。如果IAlgo因新增某些方法而更改為IAlgo2,則此庫的使用者不會受到影響。
理想情況下,庫暴露的介面不應有任何破壞性更改,並且當庫中引入更改時,使用者不應受到影響。泛型方法最適合此類約束,因為它在需要更改時非常寬容。
使用的外部API
以下是memcache++使用的外部型別:
memcache++主要使用boost和STL來實現其需求。以下是一些使用的boost特性:
- 多執行緒。
- 演算法。
- spirit。
- asio。
- 單元測試。
當然,還有眾所周知的shared_ptr用於RIIA慣用法。
並且在STL中,主要使用容器。
那麼,最終使用泛型方法的優點是什麼?
- memcache++設計選擇效率的第一個指標是程式碼行數(LOC),只有大約600行程式碼,這個結果主要歸因於兩個原因:
- 使用泛型方法消除了樣板程式碼。
- 利用了boost和stl的豐富性。
- 第二個優點是其靈活性,任何更改只會影響一小部分程式碼。
使用此類方法的缺點
許多開發人員發現泛型方法非常複雜,並且理解程式碼變得非常困難。
該怎麼做?使用還是不使用泛型方法?
如果使用這種方法設計應用程式非常困難,但使用這種方法的庫卻更容易,就像使用STL或boost一樣。
因此,即使我們想避免使用現代方法設計應用程式的風險,使用STL或boost等庫也是一個好主意。
附件:[dependency1[1].png] [externals1[1].png] [functors[1].png] [generic[1].png] [genericmethods[1].png] [inheritence[1].png] [interface[1].png] [namespaces[1].png] [notgeneric[1].png] [notgenerictreemap[1].png] [policies2[1].png] [virtual[1].png]