創建型設計模式有: 共6種
-
- 簡單工廠模式(Simple Factory)
-
- 工廠方法模式(Factory Method)
-
- 抽象工廠模式(Abstract Factory)
-
- 建造者模式(Builder)
-
- 原型模式(Prototype)
-
- 單例模式(Singleton)
簡單工廠模式
功能:主要用於創建對象。新添加類時,不會影響以前的系統代碼。核心思想是用一個工廠來根據輸入的條件產生不同的類,然后根據不同類的virtual函數得到不同的結果。
優點: 適用於不同情況創建不同的類時
缺點: 客戶端必須要知道基類和工廠類,耦合性差
模式結構:
-
- Factory:工廠角色負責實現創建所有實例的內部邏輯
-
- Product:抽象產品角色是所創建的所有對象的父類,負責描述所有實例所共有的公共接口
-
- ConcreteProduct:具體產品角色是創建目標,所有創建的對象都充當這個角色的某個具體類的實例。
模式應用:
C++舉例:
//基類 class COperation { public: int m_nFirst; int m_nSecond; virtual double GetResult() { double dResult=0; return dResult; } };
//加法 class AddOperation : public COperation { public: virtual double GetResult() { return m_nFirst+m_nSecond; } };
//減法 class SubOperation : public COperation { public: virtual double GetResult() { return m_nFirst-m_nSecond; } }; //工廠類 class CCalculatorFactory { public: static COperation* Create(char cOperator); //基類和工廠類是關聯關系 }; COperation* CCalculatorFactory::Create(char cOperator) { COperation *oper; switch (cOperator) { case '+': oper=new AddOperation(); break; case '-': oper=new SubOperation(); break; default: oper=new AddOperation(); break; } return oper; } //客戶端 int main() { int a,b; cin>>a>>b; COperation * op=CCalculatorFactory::Create('-'); //靜態函數直接調用 op->m_nFirst=a; op->m_nSecond=b; cout<<op->GetResult()<<endl; return 0; }
Java舉例:
工廠方法模式(多態工廠模式)
功能:定義一個用於創建對象的接口,讓子類決定實例化哪一個類。工廠方法使一個類的實例化延遲到其子類。
優點:
- 向客戶隱藏了哪種具體產品將被實例化的細節,用戶只需要關心所需產品對應的工廠,無須關心創建細節,無須知道具體產品類的類名。
- 基於工廠角色和產品角色的多態性設計是工廠方法模式的關鍵。它能夠使工廠可以自主確定創建何種產品對象,而如何創建這個對象的細節則完全封裝在具體工廠內部。工廠方法模式之所以又被稱為多態工廠模式,是因為所有的具體工廠類都具有同一抽象父類。
- 系統中加新產品時,無須修改抽象工廠和抽象產品的接口,(但要修改客戶端,以選擇不同的具體工廠!),無須修改其他的具體工廠和產品,而只要添加一個具體工廠和具體產品即可。符合“開閉原則”。
缺點:
- 在添加新產品時,需要編寫新的具體產品類,而且還要提供與之對應的具體工廠類,系統中類的個數將成對增加,一定程度上增加了系統復雜度,有更多的類需要編譯和運行,會給系統帶來一些額外的開銷。
- 由於考慮到系統的可擴展性,需要引入抽象層,在客戶端代碼中均使用抽象層進行定義,增加了系統的抽象性和理解難度,且在實現時可能需要用到DOM、反射等技術,增加了系統的實現難度。
模式應用:
JDBC中的工廠方法
舉例:
日志記錄器:
某系統日志記錄器要求支持多種日志記錄方式,如文件記錄、數據庫記錄等,且用戶可以根據要求動態選擇日志記錄方式, 現使用工廠方法模式設計該系統。
C++舉例:
#include <string> #include <iostream> using namespace std; //實例基類,相當於Product(為了方便,沒用抽象) class LeiFeng { public: virtual void Sweep() { cout<<"雷鋒掃地"<<endl; } }; //學雷鋒的大學生,相當於ConcreteProduct(具體產品1) class Student: public LeiFeng { public: virtual void Sweep() { cout<<"大學生掃地"<<endl; } }; //學雷鋒的志願者,相當於ConcreteProduct(具體產品2) class Volenter: public LeiFeng { public : virtual void Sweep() { cout<<"志願者"<<endl; } }; ----------------------------- //工場基類Creator class LeiFengFactory { public: virtual LeiFeng* CreateLeiFeng() //返回值體現依賴關系 { return new LeiFeng(); } }; //工場具體類1(創建大學生產品的具體工廠) class StudentFactory : public LeiFengFactory { public : virtual LeiFeng* CreateLeiFeng() //返回值體現依賴關系 { return new Student(); } }; //工場具體類2(創建志願者產品的具體工廠) class VolenterFactory : public LeiFengFactory { public: virtual LeiFeng* CreateLeiFeng() { return new Volenter(); //返回值體現依賴關系 } }; ----------------------------- //客戶端 int main() { LeiFengFactory *sf=new StudentFactory(); //由客戶端選創建什么產品 LeiFeng *s=sf->CreateLeiFeng(); s->Sweep(); delete s; delete sf; return 0; }
Java舉例:
抽象工廠模式(Kit模式)
功能:提供一個創建一系列相關或相互依賴對象的接口,而無需指定它們具體的類。
優點:
- 抽象工廠模式隔離了具體類的生成,使得客戶並不需要知道什么被創建。由於這種隔離,更換一個具體工廠就變得容易。所有的具體工廠都實現了抽象工廠中定義的那些公共接口,因此只需改變具體工廠的實例,就可以在某種程度上改變整個軟件系統的行為。另外,此模式可以實現高內聚低耦合的設計目的。
- 當一個產品族中的多個對象被設計成一起工作時,它能夠保證客戶端始終只使用同一個產品族中的對象。這對一些需要根據當前環境來決定其行為的軟件系統來說,很實用。
- 增加新的具體工廠和產品族很方便,無須修改已有系統,符合“開閉原則”。
缺點:
- 在添加新的產品對象時,難以擴展抽象工廠來生產新種類的產品,這是因為在抽象工廠角色中規定了所有可能被創建的產品集合,要支持新種類的產品就意味着要對該接口進行擴展,而這將涉及到對抽象工廠角色及其所有子類的修改,顯然會帶來較大的不便。
- 開閉原則的傾斜性(增加新的工廠和產品族容易,增加新的產品等級結構麻煩)。
模式應用:
在很多軟件系統中需要更換界面主題,要求界面中的按鈕、文本框、背景色等一起發生改變時,可以使用抽象工廠模式進行設計。
C++舉例:
#include <string> #include <iostream> #include <vector> using namespace std; //用戶抽象接口(抽象產品A) class IUser { public : virtual void GetUser()=0; virtual void InsertUser()=0; }; //部門抽象接口(抽象產品B) class IDepartment { public: virtual void GetDepartment()=0; virtual void InsertDepartment()=0; }; //ACCESS用戶(抽象產品A的產品1) class CAccessUser : public IUser { public: virtual void GetUser() { cout<<"Access GetUser"<<endl; } virtual void InsertUser() { cout<<"Access InsertUser"<<endl; } }; //ACCESS部門(抽象產品B的產品1) class CAccessDepartment : public IDepartment { public: virtual void GetDepartment() { cout<<"Access GetDepartment"<<endl; } virtual void InsertDepartment() { cout<<"Access InsertDepartment"<<endl; } }; //SQL用戶(抽象產品A的產品2) class CSqlUser : public IUser { public: virtual void GetUser() { cout<<"Sql User"<<endl; } virtual void InsertUser() { cout<<"Sql User"<<endl; } }; //SQL部門類(抽象產品B的產品2) class CSqlDepartment: public IDepartment { public: virtual void GetDepartment() { cout<<"sql getDepartment"<<endl; } virtual void InsertDepartment() { cout<<"sql insertdepartment"<<endl; } }; //抽象工廠 class IFactory { public: virtual IUser* CreateUser()=0; virtual IDepartment* CreateDepartment()=0; }; //ACCESS工廠 class AccessFactory : public IFactory { public: virtual IUser* CreateUser() { return new CAccessUser(); } virtual IDepartment* CreateDepartment() { return new CAccessDepartment(); } }; //SQL工廠 class SqlFactory : public IFactory { public: virtual IUser* CreateUser() { return new CSqlUser(); } virtual IDepartment* CreateDepartment() { return new CSqlDepartment(); } }; //客戶端: int main() { IFactory* factory= new SqlFactory(); IUser* user=factory->CreateUser(); IDepartment* depart = factory->CreateDepartment(); user->GetUser(); depart->GetDepartment(); return 0; }
Java舉例:
建造者模式(生成器模式)
功能:將一個復雜對象的構建與表示分離,使得同樣的構建過程可以創建不同的表示。
優點:
- 客戶端不必知道產品內部組成的細節,將產品本身與產品的創建過程解耦,使得相同的創建過程可以創建不同的產品對象。
- 每一個具體建造者都獨立,因此可以方便地替換具體建造者或增加新的具體建造者, 用戶使用不同的具體建造者即可得到不同的產品對象 。
- 可以更加精細地控制產品的創建過程 。將復雜產品的創建步驟分解在不同的方法中,使得創建過程更加清晰,也更方便使用程序來控制創建過程。
- 增加新的具體建造者無須修改原有類庫的代碼,指揮者類針對抽象建造者類編程,系統擴展方便,符合“開閉”。
缺點:
- 建造者模式所創建的產品一般具有較多的共同點,其組成部分相似,若產品之間的差異性很大,則不適合使用該模式,因此其使用范圍受到一定限制。
- 若產品的內部變化復雜,可能會導致需要定義很多具體建造者類來實現這種變化,導致系統變得很龐大。
模式應用:
很多游戲軟件中,地圖包括天空、地面、背景等組成部分,人物角色包括人體、服裝、裝備等組成部分,可以使用建造者模式對其進行設計,通過不同的具體建造者創建不同類型的地圖或人物。
舉例: KFC套餐
套餐是一個復雜對象,一般包含主食(如漢堡、雞肉卷等)和飲料(如果汁、 可樂等)等部分,不同的套餐有不同的組成部分,而KFC的服務員可以根據顧客的要求,一步一步裝配這些組成部分,構造一份完整的套餐,然后返回給顧客。
C++舉例:
#include <string> #include <iostream> #include <vector> using namespace std; //最終的產品類 class Product { private: vector<string> m_product; public: void Add(string strtemp) { m_product.push_back(strtemp); } void Show() { vector<string>::iterator p=m_product.begin(); while (p!=m_product.end()) { cout<<*p<<endl; p++; } } }; //建造者基類 class Builder { public: virtual void BuilderA()=0; virtual void BuilderB()=0; virtual Product* GetResult()=0; }; //第一種建造方式 class ConcreteBuilder1 : public Builder { private: Product* m_product; //體現依賴關系 public: ConcreteBuilder1() { m_product=new Product(); } virtual void BuilderA() { m_product->Add("one"); } virtual void BuilderB() { m_product->Add("two"); } virtual Product* GetResult() { return m_product; } }; //第二種建造方式 class ConcreteBuilder2 : public Builder { private: Product * m_product; //體現依賴關系 public: ConcreteBuilder2() { m_product=new Product(); } virtual void BuilderA() { m_product->Add("A"); } virtual void BuilderB() { m_product->Add("B"); } virtual Product* GetResult() { return m_product; } }; //指揮者類 class Direct { public: void Construct(Builder* temp) //構建一個使用Builder接口的對象 { temp->BuilderA(); temp->BuilderB(); } }; //客戶端 int main() { Direct *p=new Direct(); Builder* b1=new ConcreteBuilder1(); Builder* b2=new ConcreteBuilder2(); p->Construct(b1); //調用第一種建造方式 Product* pb1 = b1->GetResult(); pb1->Show(); p->Construct(b2); //調用第二種建造方式 Product * pb2 = b2->GetResult(); pb2->Show(); return 0; }
單例模式
功能:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
優點:
- 提供了對唯一實例的受控訪問。因為單例類封裝了它的唯一實例,所以它可以嚴格控制客戶怎樣以及何時訪問它,並為設計及開發團隊提供了共享的概念。
- 由於在系統內存中只存在一個對象,因此可以節約系統資源,對於一些需要頻繁創建和銷毀的對象,單例模式無疑可以提高系統的性能。
- 允許可變數目的實例。我們可以基於單例模式進行擴展,使用與單例控制相似的方法來獲得指定個數的對象實例。
缺點:
- 由於單例模式中沒有抽象層,因此單例類的擴展有很大的困難。
- 單例類的職責過重,在一定程度上違背了“單一職責原則”。因為單例類既充當了工廠角色,提供了工廠方法,同時又充當了產品角色,包含一些業務方法,將產品的創建和產品的本身的功能融合到一起。
- 濫用單例將帶來一些負面問題,如為了節省資源將數據庫連接池對象設計為單例類,可能會導致共享連接池對象的程序過多而出現連接池溢出;現在很多面向對象語言(如Java、C#)的運行環境都提供了自動垃圾回收的技術,因此,如果實例化的對象長時間不被利用,系統會認為它是垃圾,會自動銷毀並回收資源,下次利用時又將重新實例化,這將導致對象狀態的丟失。
模式應用:
一個具有自動編號主鍵的表可以有多個用戶同時使用,但數據庫中只能有一個地方分配下一個主鍵編號,否則會出現主鍵重復,因此該主鍵編號生成器必須具備唯一性,可以通過單例模式來實現。
舉例:
在OS中,打印池(Print Spooler)是一個用於管理打印任務的應用程序,通過打印池用戶可以刪除、中止或者改變打印任務的優先級,在一個系統中只允許運行一個打印池對象,如果重復創建打印池則拋出異常。現使用單例模式來模擬實現打印池的設計。
C++舉例:
懶漢式單例:
#include <iostream> #include <string> #include <vector> using namespace std; class Singelton { private: Singelton(){ } //構造函數私有堵死了外界創建副本的可能!!! static Singelton* singel; //私有的靜態全局變量來保存該類的唯一實例 public: static Singelton* GetInstance() //獲得本類實例的唯一全局訪問點 { if(singel == NULL) { singel = new Singelton(); } return singel; } }; Singelton* Singelton::singel = NULL;//注意靜態變量類外初始化 //客戶端: int main() { Singelton* s1=Singelton::GetInstance(); Singelton* s2=Singelton::GetInstance(); if(s1 == s2) //比較兩次實例化后的結果是實例相同!!! cout<<"ok"<<endl; else cout<<"no"<<endl; return 0; }
Java舉例:
原型模式
功能:用原型實例指定創建對象的種類,並通過拷貝這些原型創建新的對象。原型模式其實就是從一個對象創建另外一個可定制的對象,而且不需知道任何創建的細節。
優點:
一般在初始化的信息不發生變化的情況下,克隆是最好的辦法,既隱藏了對象創建細節,又提高性能。其等於是不用重新初始化對象,而是動態地獲得對象運行時的狀態。
缺點:
模式應用:
C++舉例
#include<iostream> #include <vector> #include <string> using namespace std; //抽象基類 class Prototype { private: string m_strName; public: Prototype(string strName){ m_strName = strName; } Prototype() { m_strName = " "; } void Show() { cout<<m_strName<<endl; } virtual Prototype* Clone() = 0 ; //關鍵就在於這樣一個抽象方法 } ; // class ConcretePrototype1 class ConcretePrototype1 : public Prototype { public: ConcretePrototype1(string strName) : Prototype(strName){} ConcretePrototype1(){} virtual Prototype* Clone() { ConcretePrototype1 *p = new ConcretePrototype1() ; *p = *this ; //復制對象 return p ; } } ; // class ConcretePrototype2 class ConcretePrototype2 : public Prototype { public: ConcretePrototype2(string strName) : Prototype(strName){} ConcretePrototype2(){} virtual Prototype* Clone() { ConcretePrototype2 *p = new ConcretePrototype2() ; *p = *this ; //復制對象 return p ; } } ; //客戶端 int main() { ConcretePrototype1* test = new ConcretePrototype1("小王"); ConcretePrototype2* test2 = (ConcretePrototype2*)test->Clone(); test->Show(); test2->Show(); return 0; }