C++設計模式類庫 Loki介紹與用法Loki是由Andrei編寫的一個與《ModernC++Design》(C++設計新思維)一書配套發行的C++代碼庫。它不僅把C++模板的功能發揮到了極致,而且把類似設計模式這樣思想層面的東西通過庫來提供。本篇文章介紹如何利用Loki來輕鬆地實現一些設計模式。由於Loki使用了大量牛X到爆的模板技巧,對編譯器的要求是很苛刻的,官方兼容列表裡只列出了VC7.1以上版本及GCC3.4以上版本。如果你象我一樣喜歡用C++Builder6或VC6,可以去下載《Modern C++ Design》配套源碼,那裡面的Loki提供了對其它不兼容編譯器的移植代碼,只是版本低了一點,有些介面有些差別。最後,BTW,《Modern C++ Design》確實是一本好書,在這裡也順便推薦一下^_^Loki的下載地址是http://sourceforge.net/projects/loki-lib/,目前最新版本是Loki 0.1.7,後面的代碼都使用這個版本作為測試標準。編譯Loki庫提供了N多種編譯途經,你可以直接打開項目文件(VC、Code::Block、Cpp-Dev等IDE)編譯,也可以用傳統的makefile來make,還可以直接用批處理文件編譯。象我這種被IDE慣壞的人,一般都是直接把src目錄里的代碼加入到項目中了事。Singleton模式(單件模式)Singleton模式確保一個類在系統中只有一個實例。比如一個窗口系統中只能有一個滑鼠對象,只有一個屏幕對象,一個剪切板對象...。我們可以用一個全局變數來做這些工作,但它不能防止實例化多個對象。一個更好的辦法是讓類自身保存它的唯一實例,並且不允許創建其它實例,這就是Singleton模式。Loki庫的SingletonHolder類提供了對Singleton模式的支持頭文件#include類型定義template< typenameT, templateclassCreationPolicy=CreateUsingNew, templateclassLifetimePolicy=DefaultLifetime, templateclassThreadingModel=::Loki::SingleThreaded, classMutexPolicy=::Loki::Mutex> classLoki::SingletonHolder;Loki的類大部分都是基於策略編程的,其中最主要的是CreationPolicy,它決定了怎樣生成一個類實例,可選的有:template class Alloc> struct CreateUsing; 在分配器分配的內存中生成實例,如 template struct CreateStatic 生成靜態實例 template struct CreateUsingMalloc 使用malloc申請內存並在其中生成實例 template struct CreateUsingNew 使用new生成實例(默認)示例代碼classMyClass{ public: //有默認構造 MyClass(){;} //顯示自己所在的內存地址,用以區分是否是同一個對象 voidShowPtr() { std::cout<MyClassSingleton; int_tmain(intargc,_TCHAR*argv[]) { //通過Instance()靜態方法取得MyClass實例 MyClass&v=MyClassSingleton::Instance(); v.ShowPtr(); //MyClassSingleton::Instance()總是返回同一個MyClass實例 MyClassSingleton::Instance().ShowPtr(); return0; }Loki::SingletonHolder默認的CreationPolicy策略要求類必須有默認構造,如MyClass這樣。如果需要包裝沒有默認構造的類的話,我們就得自定義一個CreationPolicy策略,好在CreationPolicy策略比較簡單,先看看Loki中默認的CreateUsingNew吧:templatestructCreateUsingNew { staticT*Create() {returnnewT;} staticvoidDestroy(T*p) {deletep;} };呵呵,簡單吧,只是簡單的Create和Destroy而已。我們只要修改Create()靜態方法,new一個自己的對象就可以了,當然隨便多少構造參數都可以在這裡寫上去啦。另外,如有必要,也可以做一些其它初始工作哦。classMyClass2{ public: //構造里要求兩個整數 MyClass2(int,int){;} voidShowPtr() { std::cout< classCreateMyClass2UsingNew: publicLoki::CreateUsingNew { public: staticT*Create() {returnnewT(0,0);} }; //定義使用CreateMyClass2UsingNew策略的Singleton類 typedefLoki::SingletonHolderMyClass2Singleton; //使用之 int_tmain(intargc,_TCHAR*argv[]) { MyClass2Singleton::Instance().ShowPtr(); MyClass2Singleton::Instance().ShowPtr(); return0; }usidc52011-01-18 16:48對象工廠 Object Factory又名簡單工廠模式,貌似不屬於設計模式範疇。它的作用是把對象的創建工作集中起來,並使創建工作與其它部分解耦。比如下面這個函數也可當作簡單工廠:CWinBase*Create(strings) { if(s=="Edit") returnnewCEdit; elseif(s=="Button") returnnewCButton; ... }Loki庫的Factory類提供了對簡單工廠模式的支持。頭文件#include類型template< classAbstractProduct,//「產品」基類型 typenameIdentifierType,//用什麼區分各產品 typenameCreatorParmTList=NullType,//生成器參數 templateclassFactoryErrorPolicy=DefaultFactoryError> classLoki::Factory;成員方法bool Register(const IdentifierType& id, ProductCreator creator);以id作為識別碼註冊生成器。函數、對象方法或仿函數都可以作為生成器。bool Unregister(const IdentifierType& id);取消註冊std::vectorRegisteredIds();取得已註冊的所有識別碼AbstractProduct* CreateObject(const IdentifierType& id);AbstractProduct* CreateObject(const IdentifierType& id, Parm1 p1);AbstractProduct* CreateObject(const IdentifierType& id, Parm1 p1, Parm2 p2);...按識別碼id生成對象(調用對應的生成器)示例代碼#include #include #include #include //窗體基類 structIWidget{ virtualvoidprintName()=0; virtual~IWidget(){;} }; //定義窗體工廠,使用string區分各對象類型 typedefLoki::Factorywidget_factory_t; //按鈕窗體 structCButton:IWidget{ voidprintName() { std::cout<printName(); deletepWid; } { IWidget*pWid=wf.CreateObject("ListBox"); pWid->printName(); deletepWid; } return0; }很多時候,工廠往往只需要一個實例,我們可以使用前面說過的SingletonHolder把widget_factory_t弄成Singleton模式。上面生成CButton之類的窗體時使用的是默認構造,所以我偷懶沒寫各個類的生成器,直接用了Loki::CreateUsingNew。如果你的類有構造參數的話,那麼就得在Loki::Factory模板參數中指出參數類型,並且自定義生成器,就象這樣:#include #include #include //窗體基類 structIWidget{ virtualvoidprintName()=0; virtual~IWidget(){;} }; //定義窗體工廠,這裡用Loki::Seq指定參數類型 typedefLoki::Factory< IWidget, std::string, Loki::Seq >widget_factory_t; //單件模式,注意Lifetime策略要選擇Loki::LongevityLifetime::DieAsSmallObjectChild,否則... typedefLoki::SingletonHolder Loki::LongevityLifetime::DieAsSmallObjectChild>Singleton_Fac; //按鈕窗體 structCButton:IWidget{ voidprintName() { std::cout<structCreateT { T*operator()(std::stringtxt,intp1,charp2)const { returnnewT(txt,p1,p2); } }; int_tmain(intargc,_TCHAR*argv[]) { //工廠實例 widget_factory_t&wf=Singleton_Fac::Instance(); //註冊各種窗體的生成器,用我們的生成器 wf.Register("Edit",CreateT()); wf.Register("Button",CreateT()); wf.Register("ListBox",CreateT()); //測試,使用工廠生成窗體 { IWidget*pWid=wf.CreateObject("Edit","Hello",0,""); pWid->printName(); deletepWid; } { IWidget*pWid=wf.CreateObject("ListBox","World",0,""); pWid->printName(); deletepWid; } return0; }Loki::Seq是一個類似於TypeList的東東,可以存放一系列的類型。另外用SingletonHolder包裝Factory時,一定要用Loki::LongevityLifetime::DieAsSmallObjectChild作為SingletonHolder的lifttime策略(Loki使用說明上說的,由於Factory使用了Loki內部的內存管理器SmallObject)。usidc52011-01-18 16:49Abstract Factory模式(抽象工廠)抽象工廠提供了一個創建一系列相關或相互依賴對象的介面,而無需指定具體的類。比如要編寫一個可換膚的用戶界面,那麼我們生成的每個窗體都應該遵循統一的風格,不應該在一個界面里同時出現Mac風格和Win風格的按鈕。為了便於控制,我們可以這樣寫代碼:structIWidgetFactory{ virtualIButton*CreateButton()=0; virtualIEdit*CreateEdit()=0; virtualIListBox*CreateListBox()=0; }; structCWindowsFactory:IWidgetFactory{ virtualIButton*CreateButton(){生成Win風格按鈕;} virtualIEdit*CreateEdit(){生成Win風格編輯框;} virtualIListBox*CreateListBox(){生成Win風格列表框;} }; structCMacFactory:IWidgetFactory{ virtualIButton*CreateButton(){生成Mac風格按鈕;} virtualIEdit*CreateEdit(){生成Mac風格編輯框;} virtualIListBox*CreateListBox(){生成Mac風格列表框;} };這樣,在程序中我們要用執有IWidgetFactory指針,在必要時實例化某個具體風格的工廠類,最後所有的窗體都由這個IWidgetFactory指針來生成即可。這就是Abstract Factory模式。Loki庫的AbstractFactory和ConcreteFactory提供了對Abstract Factory模式的支持。頭文件#include類型template< class TList, template class Unit = AbstractFactoryUnit>class AbstractFactory;AbstractFactory模板類是一個虛類,它根據TList中的類型提供一組Create<>()方法。模板參數TList是一個Typelist,輸入所有工廠可生產的產品基類,如前面的IButton,IEdit,IListBox。 模板參數Unit依據TList中的所有類型產生對應的Create<>()虛函數,一般直接用默認的AbstractFactoryUnit就可以了。template< class AbstractFact, template class Creator = OpNewFactoryUnit, class TList = typename AbstractFact::ProductList>class ConcreteFactory;ConcreteFactory實現了AbstractFactory中的Create<>()方法模板參數AbstractFact就是對應的AbstractFactory類 模板參數Creator是產品實例的生成策略,可選的有OpNewFactoryUnit和PrototypeFactoryUnit。 OpNewFactoryUnit使用new生成新的產品實例; PrototypeFactoryUnit使用已有產品(原型)的Clone()方法生成新的實例,這也意味著要使用PrototypeFactoryUnit我們的產品基類必須要有T *Clone()成員方法。TList提供一組具體的產品類型,如果Creator策略是PrototypeFactoryUnit,可以不提供。演示代碼#include #include #include //產品基類 structIButton{ virtualvoidclick()=0; }; structIEdit{ virtualvoidedit()=0; }; structIListBox{ virtualvoidscroll()=0; }; //抽象工廠 typedefLoki::AbstractFactory< LOKI_TYPELIST_3(IButton,IEdit,IListBox)//也可以用Loki::Seq::Type >IWidgetFactory; //具體產品-Win structCWinBtn:IButton{ virtualvoidclick(){ std::cout<CWindowsFactory; typedefLoki::ConcreteFactory< IWidgetFactory, Loki::OpNewFactoryUnit, LOKI_TYPELIST_3(CMacBtn,CMacEdit,CMacLB) >CMacFactory; //使用工廠生成的產品 voidUsingWidget(IWidgetFactory*pFac) { IEdit*pEdt=pFac->Create(); IButton*pBtn=pFac->Create(); IListBox*pLB=pFac->Create(); pEdt->edit(); pBtn->click(); pLB->scroll(); deletepEdt; deletepBtn; deletepLB; } int_tmain(intargc,_TCHAR*argv[]) { //使用Win風格 { CWindowsFactorywinfac; UsingWidget(&winfac); } //使用Mac風格 { CMacFactorymacfac; UsingWidget(&macfac); } return0; }usidc52011-01-18 16:49Visitor 模式(訪問者模式)訪問者模式把對某對象結構中的各元素的相關操作集中到一起,可以方便地在不改變無素類的前提下添加新的操作。假設一個XML類,我們可能要列印、保存、轉換這個XML數據。實際上這些操作都需要遍歷節點,不同的只是操作的方式,我們可以這樣寫代碼:structIVisitor; structTTextNode{ virtualvoidaccept(IVisitor*visitor){visitor->visit(this);} }; structTCDataNode{ virtualvoidaccept(IVisitor*visitor){visitor->visit(this);} }; structTElementNode{ virtualvoidaccept(IVisitor*visitor){visitor->visit(this);} }; structIVisitor{ virtualvoidvisit(TTextNode*)=0; virtualvoidvisit(TCDataNode*)=0; virtualvoidvisit(TElementNode*)=0; }; structCSaveVisitor{ virtualvoidvisit(TTextNode*){保存文本內容} virtualvoidvisit(TCDataNode*){保存[[CData內容]]} virtualvoidvisit(TElementNode*){保存所有子節點.accept(this)} }; structCPrintVisitor{ ... };當然,這種模式的問題是可能會破壞一些封裝性。我們這裡不討論Visitor模式的優劣,先看看Loki是怎麼實現這個模式的吧頭文件#include類型template< typename R = void, template class CatchAll = DefaultCatchAll, bool ConstVisitable = false>class BaseVisitable;可訪問類型的基類,如果要讓元素能被訪問,要必須繼承自BaseVisitable。模板參數R是Accept()方法的返回值。 模板參數CatchAll表示某元素沒有對應的訪問方法時的反應,默認的DefaultCatchAll是什麼也不做。 模板參數ConstVisitable表示Accept()方法是否是const方法。class BaseVisitor;所有訪問者的基類template < class T, typename R = void, bool ConstVisit = false>class Visitor;訪問者實現模板模板參數T是一個Typelist,表示訪問者能接受的所有元素類型 模板參數R是Visit()方法的返回值 模板參數ConstVisit表示Visit()方法是否是const方法。示例代碼#include #include #include #include #include //XML元素 structTTextNode:Loki::BaseVisitable<>{ LOKI_DEFINE_VISITABLE(); TTextNode(std::stringtext) :m_text(text){} std::stringm_text; }; structTCDataNode:Loki::BaseVisitable<>{ LOKI_DEFINE_VISITABLE(); TCDataNode(std::stringtext) :m_cdata(text){} std::stringm_cdata; }; structTElementNode:Loki::BaseVisitable<>{ LOKI_DEFINE_VISITABLE(); std::stringm_name; TElementNode(std::stringtext) :m_name(text){} typedefLoki::BaseVisitable<>visitable_t; //這裡用了Loki::SmartPtr,這是一個智能指針類型 typedefLoki::SmartPtr< Loki::BaseVisitable<> >ptr_t; std::vectorm_childs; }; //訪問者,必須繼承自BaseVisitor classCPrintVisitor: publicLoki::BaseVisitor, publicLoki::Visitor { public: voidVisit(TTextNode&n){std::cout<";} voidVisit(TElementNode&n){ std::cout< std::cout<"< { n.m_childs[idx]->Accept(*this); } std::cout<"<m_childs.push_back(newTTextNode("hello")); TElementNode*child2=newTElementNode("child2"); child2->m_childs.push_back(newTCDataNode("world>_usidc52011-01-18 16:51大牛Andrei Alexandrescu的《Modern C++ Design》討論的是C++語言的最前沿研究:generative programming。本書中譯版估計得要半年以後才能出來,所以只能靠其所附源碼來窺測generative programming了。目前,我剛將源碼讀解了約一半,等全部讀完,我會將我的讀解注釋放出來的。現在,現談一下我的感想。先扯得遠一點。C++有兩個巨大優點:和C兼容,自由;有兩個巨大缺點:和C兼容,複雜。C++極其複雜,很難掌握,而這正是「自由」的代價。C++語言是個多編程風格的語言,它同時支持過程化、基於對象、面向對象、泛型、生成性這5種編程思想,具有極其強大的表達能力,可以方便地將各種設計轉化為實現。generic Programming的思想精髓是基於介面編程(相對於OOP,連多態所需的基類都不要了),它的技術出發點是選擇子,核心技術是:類型推導、類型萃取、特化/偏特化,其成果是STL庫:一組通用容器和一組操作於通用容器上的通用演算法。generative programming的思想精髓是基於策略編程(編譯器根據策略自動生成所需代碼,由於具有更高的抽象性,所以代碼復用度也更高),在Loki庫的實現中,目前只使用了遞歸策略,它的技術出發點是Typelist,核心技術是:類型推導、類型萃取、特化/偏特化、多重繼承、類型間去耦合,其成果是Loki庫:對設計模式的封裝。Typelist是一種對類型本身進行存儲和管理的技巧,它的源碼已經貼過了,我也作了註解,此處不再談論。這是多重繼承在COM之後的又一大型運用。多重繼承極易發生菱型缺陷,所以Loki庫使用了類型間去耦合技術來避免:template struct Type2Type{typedef T OriginalType;};經過這樣一層轉換後,原類型T間的各種轉換關係(尤其是繼承/派生關係)已不復存在,菱型缺陷不會再發生了。Loki庫的具體實現相當講究技巧,設計它非常困難(難度遠大於STL庫,和Boost庫有得一拼啊)。但使用它卻非常容易,而且便利顯著。由於Loki庫提供了對設計模式的封裝,所以極大量地豐富了C++語言的表達能力,使的你的設計更容易地轉化為實現。目前,Loki庫只提供了對廠模式和visitor模式的封裝,它還處於發展初期。
推薦閱讀:

查看原文 >>
相关文章