C++設計模式之Adapter 一、功能 将一個類的接口轉換成客戶希望的另外一個接口,解決兩個已有接口之間不比對的問題。Adapter模式使得原本由于接口不相容而不能一起工作的那些類可以一起工作。 二、結構圖 (1)class adapter C++設計模式 (2)object adapter C++設計模式 三、實作 和其他很多模式一樣,學習設計模式的重點是學習每種模式的思想,而不應拘泥于它的某種具體結構圖和實作。因為模式是靈活的,其實作可以是千變萬化的,隻是所謂萬變不離其宗。在STL中大量運用了Adapter模式,象function adapter、iterator adpter,它們與這裡說的adapter結構并不一樣,但思想是一樣的。具體的介紹可到侯捷網站上找相關文章,他講得非常好。 四、示例代碼 (1)class adapter namespace DesignPattern_Adapter { // class Adaptee class Adaptee public: void SpecialRequest() {} } ; // class Target class Target virtual void Request() = 0 ; // class Adapter class Adapter : public Target, private Adaptee virtual void Request() { SpecialRequest() ; } } ; } 用戶端代碼: using namespace DesignPattern_Adapter ; Target *p = new Adapter() ; p->Request() ; //實際上調用的是Adaptee::SpecialRequest() } (2)object adapter namespace DesignPattern_Adapter class Adapter : public Target virtual void Request() { _adaptee.SpecialRequest() ; } private: Adaptee _adaptee ; 用戶端代碼: 六、執行個體 (1)STL中的Class Adapter STL中的Adapter Class包括:a.stack(對應的adaptee是deque)。b.queue(對應的adaptee是deque)。 c.priority_queue(對應的adaptee是vector)。 下面是從VC中的< stack >拷出的stack的類定義: template class _Container = deque<_Ty> > class stack { // LIFO queue implemented with a container typedef _Container container_type; typedef typename _Container::value_type value_type; typedef typename _Container::size_type size_type; stack() : c() { // construct with empty container explicit stack(const _Container& _Cont) : c(_Cont) { // construct by copying specified container bool empty() const { // test if stack is empty return (c.empty()); size_type size() const { // test length of stack return (c.size()); value_type& top() { // return last element of mutable stack return (c.back()); const value_type& top() const { // return last element of nonmutable stack void push(const value_type& _Val) { // insert element at end c.push_back(_Val); void pop() { // erase last element c.pop_back(); bool _Eq(const stack<_Ty, _Container>& _Right) const { // test for stack equality return (c == _Right.c); bool _Lt(const stack<_Ty, _Container>& _Right) const { // test if this < _Right for stacks return (c < _Right.c); protected: _Container c; // the underlying container }; 關鍵之處在于_Container c,stack所有的操作都轉交給c去處理了。(這實際上就是前面所說的"object adapter",注意STL中的class adapter與上面所說的class adapter概念不完全一緻) stack的使用方法很簡單,如下: int ia[] = { 1,3,2,4 }; deque id(ia, ia+4); stack is(id); (2)近日看了一篇文章“Generic< Programming >:簡化異常安全代碼”,原文出自http://www.cuj.com/experts/1812/alexandr.htm?topic= experts, 中文譯文出自"C++ View第5期"。文章絕對一流,作者給出的代碼中也使用了Adaptor模式,也有一定代表性。我将其問題一般化,概括出以下示例: 問題:假設有幾個已有類,他們有某些共同的行為,但它們彼此間是獨立的(沒有共同的基類)。如: class T1 void Proc() {} class T2 // ... 如何以統一的方式去調用這些行為呢? 解決方法1:很自然的會想到用模闆,如: template <class T> void Test(T t) { t.Proc() ; } 的确不錯,但這隻适用于簡單的情況,有時情況是很複雜的,比如我們無法把類型放到模闆參數中! 解決方法2:困難來自于這些類沒有共同的基類,是以我們就創造一個基類,然後再Adapt。 // class IAdaptor,抽象基類 class IAdaptor public: virtual void Proc() = 0 ; } ; // class Adaptor class Adaptor : public IAdaptor, private T //實作繼承 virtual void Proc() { T::Proc() ; } } ; // 以統一方式調用函數Proc,而不關心是T1、T2或其他什麼類 void Test(const std::auto_ptr& sp) sp->Proc() ; } 用戶端代碼: Test(std::auto_ptr(new Adaptor)) ; Test(std::auto_ptr(new Adaptor)) ; 上例很簡單,用方法一中的模闆函數就可以很好地解決了。下面是一個略微複雜一點的例子,根據參數類型來建立适當的對象: T1(int) { /*...*/ } void Proc() { /*...*/ } T2(char) { /*...*/ } // class IAdaptor,抽象基類 class IAdaptor virtual void Proc() = 0 ; // class Adaptor template class Adaptor : public IAdaptor, private T //實作繼承 Adaptor(int n) : T(n) {} Adaptor(char c) : T(c) {} virtual void Proc() { T::Proc() ; } class Test Test(int n) : sp(new Adaptor(n)) {} Test(char c) : sp(new Adaptor(c)) {} void Proc() { sp->Proc() ; } std::auto_ptr sp ; Test t1(10) ; t1.Proc() ; Test t2('c') ; t2.Proc() ; 上面是示例而非執行個體,你也許更願意看看它實際的運用。去下載下傳作者所寫的代碼,好好欣賞一下吧。 C++設計模式之Abstract Factory 一、功能 提供一個建立一系列相關或互相依賴對象的接口,而無需指定它們具體的類。 類廠最基本的結構示意圖如下: C++設計模式 在實際應用中,類廠模式可以擴充到很複雜的情況,如下圖所示: C++設計模式 三、優缺點 優點:(1)封裝建立過程。客戶不用知道類廠是如何建立類執行個體的,類廠封閉了所有建立的細節。這樣可選擇不同的建立方法,增加了靈活性。 (2)将客戶與具體類隔離,提高了各自的可重用性。 缺點:Factory類層次與具體類層次通常是平行的(即一一對應的)。增加一個具體類,一般也要相應地增加一個factory類,增加了系統複雜度。 四、實作 (1)Abstract Factory類中通常是一組Factory Method的集合。個人認為與Factory Method模式沒有本質差別。 (2)通常可以把工廠作為單件。 五、示例代碼 namespace DesignPattern_AbstractFactory class AbstractProductA {}; // Product A class ProductA1 : public AbstractProductA {}; class ProductA2 : public class AbstractProductB {}; // Product B class ProductB1 : public AbstractProductB {}; class ProductB2 : public class AbstractFactory { public: virtual AbstractProductA* CreateProductA() = 0 ;// 建立ProductA virtual AbstractProductB* CreateProductB() = 0 ;// 建立ProductB class ConcreteFactory1 : public AbstractFactory { return new ProductA1() ; } ProductB1() ; } static ConcreteFactory1* Instance() { static ConcreteFactory1 instance ; return &instance ; } protected: ConcreteFactory1() {} private: ConcreteFactory1(const ConcreteFactory1&) ; ConcreteFactory1& operator=(const ConcreteFactory1&) ; } ; class ConcreteFactory2 : public ProductA2() ; } ProductB2() ; } static ConcreteFactory2* Instance() { static ConcreteFactory2 instance ; return &instance ; } protected: ConcreteFactory2() {} ConcreteFactory2(const ConcreteFactory2&) ConcreteFactory2& operator=(const ConcreteFactory2&) ; using namespace DesignPattern_AbstractFactory ; // 第一種建立方法 AbstractFactory *pFactory = ConcreteFactory1::Instance() ; AbstractProductA *pProductA = pFactory->CreateProductA() ; AbstractProductB *pProductB = pFactory->CreateProductB() ; // 第二種建立方法 pFactory = ConcreteFactory2::Instance() ; pProductA = pFactory->CreateProductA() ; pProductB = pFactory->CreateProductB() ; 最早知道類廠的概念是在COM中,但當時也沒想到這是如此重要的一種模式,在許多其他模式中都可以用到類廠模式。 COM中不能直接建立元件,這也是由COM的一個特性決定的:即客戶不知道要建立的元件的類名。 C++設計模式之Singleton 保證一個類僅有一個執行個體。 C++設計模式 Singleton模式是做為"全局變量"的替代品出現的。是以它具有全局變量的特點:全局可見、貫穿應用程式的整個生命期,它也具有全局變量不具備的性質:同類型的對象執行個體隻可能有一個。 教科書上的Singleton定義如下: class Singleton static Singleton* Instance() ; Singleton() {} static Singleton *_instance ; Singleton(const Singleton&) ; Singleton& operator=(const Singleton&) ; Singleton* Singleton::_instance = NULL ; Singleton* Singleton::Instance() (_instance == NULL) ? _instance = new Singleton() : 0 ; //lazy initialization return _instance ; (1)因為傳回的是指針,為防止使用者調用delete函數,可把static Singleton *_instance;改為在Instance()中定義static Singleton _instance。這樣顯然更安全,同時也具有lazy initialization的特性(即第一次通路時才建立)。 (2)假設需要從Singleton派生子類,而子類也需要有同樣的性質,既隻能建立一個執行個體。我覺得,這很難辦。根本原因在于Instance()函數不是虛函數,不具有多态的性質。一種常用方法是把 Instance()函數移到子類中,這時就隻能用static Singleton *_instance,而不能用static Singleton _instance了,除非把_instance也要移到子類,無論怎麼做都不優雅。另一種方法是用模闆。具體用什麼方法,隻能根據實際情況權衡。 (1)沒子類的情況 namespace DesignPattern_Singleton { static Singleton* Instance() { static Singleton _instance ; return &_instance ; } using namespace DesignPattern_Singleton ; Singleton *p = Singleton::Instance() ; ...... (2)有子類的情況 方法一: namespace DesignPattern_Singleton // class Singleton Singleton* Singleton::_instance = NULL ; // class ConcreteSingleton class ConcreteSingleton : public Singleton ConcreteSingleton() {} Singleton* ConcreteSingleton::Instance() (_instance == NULL) ? _instance = new ConcreteSingleton() : 0 ; Singleton *p = ConcreteSingleton::Instance() ; 方法二: static Singleton* Instance() { static ConcreteSingleton _instance ; return &_instance ; } 方法三: template < class T > static T* Instance() { static T _instance ; return &_instance ; } Singleton(const Singleton &) ; class ConcreteSingleton : public Singleton< ConcreteSingleton > {} ; 用戶端代碼 ConcreteSingleton *p = ConcreteSingleton::Instance() ; C++模式開發之Bridge 一、功能 将抽象部分與它的實作部分分離,使它們都可以獨立地變化。 C++設計模式 三、示例代碼 namespace DesignPattern_Bridge // class Implementor class Implementor virtual void OperationImp() = 0 ; // class ConcreteImplementorA class ConcreteImplementorA : public Implementor virtual void OperationImp() {} // class ConcreteImplementorB class ConcreteImplementorB : public Implementor // class Abstraction class Abstraction void Operation(Implementor* imp) { assert(imp) ; imp->OperationImp() ; } } using namespace DesignPattern_Bridge ; Abstraction obj ; Implementor *impa = new ConcreteImplementorA() ; Implementor *impb = new ConcreteImplementorB() ; obj.Operation(impa) ; //第一種實作方法 obj.Operation(impb) ; //第二種實作方法 四、執行個體 (1)建立可以在X Window System和IBM的Presentation Manager系統中都可以使用的視窗。(書上的例子) C++設計模式 Bridge的魅力在于抽象和實作之間是松散的關系,它們之間可以進行随意組合。如上圖中,就有IconWindow+ XWindowImp、TransientWindow+XWindowImp、IconWindow+PMWindowImp、 TransientWindow+PMWindowImp四種組合。 C++模式設計之Builder 将一個複雜對象的建構與它的表示分離,使得同樣的建構過程可以建立不同的表示。 C++設計模式 各類之間的互動關系如下圖所示: C++設計模式 namespace DesignPattern_Builder class Product1 { /*...*/ } ; class Product2 { /*...*/ } ; // class Builder class Builder //抽象基類 virtual void BuilderPartA() {} //提供預設實作 virtual void BuilderPartB() {} virtual void BuilderPartC() {} Builder() {} // class ConcreteBuilder1 class ConcreteBuilder1 : public Builder //建立Product1 ConcreteBuilder1() : _product(NULL) {} virtual void BuilderPartA() { /*...*/ } virtual void BuilderPartB() { /*...*/ } virtual void BuilderPartC() { /*...*/ } virtual Product1* GetProduct1() { return _product ; } //傳回建立的Product1對象 Product1 *_product ; // class ConcreteBuilder2 class ConcreteBuilder2 : public Builder //建立Product2 ConcreteBuilder2() : _product(NULL) {} virtual Product2* GetProduct2() { return _product ; } //傳回建立的Product2對象 Product2 *_product ; // class Director class Director //建立對象(Director并不知道具體建立出來的對象是什麼樣的,隻有調用該函數的client知道) void Construct(Builder *builder) builder->BuilderPartA() ; builder->BuilderPartB() ; builder->BuilderPartC() ; using namespace DesignPattern_Builder ; Director director ; // 建立第一種對象 ConcreteBuilder1 *pBuilder1 = new ConcreteBuilder1() ; director.Construct(pBuilder1) ; Product1 *product1 = pBuilder1->GetProduct1() ; // 建立第二種對象 ConcreteBuilder2 *pBuilder2 = new ConcreteBuilder2() ; director.Construct(pBuilder2) ; Product2 *product2 = pBuilder2->GetProduct2() ; (1)例子一。如下圖所示: C++設計模式 上圖的功能是是把一個RTF檔案轉換為多種正文格式。RTFReader進行文法分析,然後将所有的token串逐一轉換。可見builder就是一步步地把各個部分組裝為一個整體。它封閉了組裝的方法,組裝出來的對象也大相徑庭。 C++設計模式之Prototype 用原型執行個體指定建立對象的種類,并且通過拷貝這些原型建立新的對象。 C++設計模式 優點:複制自身。客戶不知道需要對象的實際類型,隻需知道它的抽象基類即可。(即有繼承樹的情況) 缺點:必須先有一個對象執行個體(即原型)才能clone。 namespace DesignPattern_Prototype // class Prototype class Prototype //抽象基類 virtual Prototype* Clone() = 0 ; // class ConcretePrototype1 class ConcretePrototype1 : public Prototype virtual Prototype* Clone() ConcretePrototype1 *p = new ConcretePrototype1() ; *p = *this ; //複制對象 return p ; // class ConcretePrototype2 class ConcretePrototype2 : public Prototype ConcretePrototype2 *p = new ConcretePrototype2() ; using namespace DesignPattern_Prototype ; ConcretePrototype1 *obj1 = new ConcretePrototype1() ;//原型對象1 ConcretePrototype2 *obj2 = new ConcretePrototype2() ;//原型對象2 Prototype *newobj1 = obj1->Clone() ;//克隆對象1 Prototype *newobj2 = obj2->Clone() ;//克隆對象2 //使用複制出的對象newobj1和newobj2 五、執行個體 在一個圖形編輯器中,每一個圖形元素,如線、圓、文字等都應該支援拷貝操作,即點中圖形,按下Ctrl+C,再按下Ctrl+V後就會複制一個新的圖形。 顯然這是一種clone操作。是以在每個從Graphic派生出的圖形子類都應運用Prototype模式,加上Clone操作。 C++設計模式之Factory Method 定義一個用于建立對象的接口,讓子類決定執行個體化哪一個類。Factory Method 使一個類的執行個體化延遲到其子類。 C++設計模式 (1)在某些情況下,比如僅僅為了建立适當的Product對象而派生新的Creator子類,并且建立不同Product的方法一緻時,可以考慮用模闆代替繼承。如: class Creator virtual Product* CreateProduct() = 0 ; }; template < class ConcreteProduct > class ConcreteCreator: public Creator virtual Product* CreateProduct() { return new ConcreteProduct() ; }; 模闆與繼承的本質差別之一是:模闆:行為不依賴于類型。繼承:行為依賴于類型。(Effective C++ Item 41) 事實上,在很多模式中都存在着可以用模闆代替繼承的情況,其根本原因就在于子類的行為是一緻的。 namespace DesignPattern_FactoryMethod class Product { /*...*/ } ; class ConcreteProduct : public Product { /*...*/ } ; // class Creator class Creator void Operate() ; void Creator::Operate() // ... Product *p = CreateProduct() ; // class ConcreteCreator class ConcreteCreator : public Creator using namespace DesignPattern_FactoryMethod ; ConcreteCreator p ; p.Operate() ; 這裡的CreateProduct其實也是一個Template Method。 Factory Method的運用太廣泛了,它經常運用在其它模式中,其執行個體舉不勝數。 (1) C++設計模式 MFC中的CDocument類就包含了類似于上圖CApplication中的三個函數。這裡的CreateDocument就是一個factory method,因為它負責建立一個文檔對象。 (2) C++設計模式 當一個類将它的一些職責委托給一個獨立的類時,就産生了平行類層次。上圖中Figure和Manipulator就是平行 類層次,Figure代表一些圖形元素,如線、文字等,Manipulator表示作用于這些圖形元素的操作,如拖拉、移動、選中等。如果這些操作所需要 的狀态資訊并不需要儲存在Figure中,那麼把Figure和Manipulator分成兩個類層次是個好主意。這裡的 CreateManipulator就是一個factory method。 C++設計模式之Composite 表示“部分-整體”關系,并使使用者以一緻的方式使用單個對象群組合對象。 C++設計模式 上圖中,也可以做些擴充,根據需要可以将Leaf和Composite做為抽象基類,從中派生出子類來。 優點:對于Composite模式,也許人們一開始的注意力會集中在它是如何實作組合對象的。但Composite最重要之處在于使用者并不關心是組合對象還是單個對象,使用者将以統一的方式進行處理,是以基類應是從單個對象群組合對象中提出的公共接口。 缺點:Composite最大的問題在于不容易限制組合中的元件。 有時需要限制組合中的元件,即希望一個Composite隻能有某些特定的Leaf。這個問題我是用多繼承和動态類型轉換來解決的。假如組合對象Composite1隻能包含單個對象ConcreteLeaf1,Composite2可以包含單個對象 ConcreteLeaf1和ConcreteLeaf2。如下圖所示: C++設計模式 上圖中的類層次比較多,使用了AbstractLeaf1和AbstractLeaf2,但沒使用 AbstractComposite1和AbstractComposite2,這個并不重要,也可以把AbstractLeaf1和 AbstractLeaf2去掉,這個并不重要,可以根據具體情況決定要不要。 簡單的代碼實作如下: namespace DesignPattern_Composite { class Component virtual void operation() = 0 ; virtual void Add(Component*) {} class AbstractComponent1 : virtual public Component {} ; class AbstractLeaf1 : virtual public AbstractComponent1 {} ; class Composite1 : public AbstractComponent1 virtual void operation() { /* do operation */ } virtual void Add(Component*) ; void Composite1::Add(Component *p) AbstractComponent1 *pc1 = dynamic_cast<ABSTRACTCOMPONENT1*>(p) ; if (pc1 == NULL) return ; // do add operation class AbstractComponent2 : virtual public Component {} ; class AbstractLeaf2 : virtual public AbstractComponent2 {} ; class Composite2 : public AbstractComponent2 void Composite2::Add(Component *p) AbstractComponent2 *pc2 = dynamic_cast<ABSTRACTCOMPONENT2*>(p) ; if (pc2 == NULL) return ; class ConcreteLeaf1 : public AbstractLeaf1 class ConcreteLeaf2 : public AbstractLeaf1, public AbstractLeaf2 { using namespace DesignPattern_Composite ; Component *pc1 = new ConcreteLeaf1() ; Component *pc2 = new ConcreteLeaf2() ; Component *pc3 = new Composite1() ; Component *pc4 = new Composite2() ; pc3->Add(pc1) ; // ok pc3->Add(pc2) ; // ok pc3->Add(pc3) ; // ok pc3->Add(pc4) ; // fail pc4->Add(pc1) ; // fail pc4->Add(pc2) ; // ok pc4->Add(pc3) ; // fail pc4->Add(pc4) ; // ok 有兩點需要注意,一是因為用了多繼承,是以需要使用virtual inheritance。二是要用dynamic_cast來判斷是否允許組合該元件。 // class Component virtual void Operation() = 0 ; // class Leaf class Leaf : public Component virtual void Operation() {} // class Composite class Composite : public Component virtual void Add(Component *p) { _list.push_back(p) ; } virtual void Operation() vector< Component* >::const_iterator it ; for (it = _list.begin(); it != _list.end(); it++) (*it)->Operation() ; private: vector< Component* > _list ; (1)JUnit中就用的是Composite模式。 C++設計模式 |