軟件領域中的設計模式為開發人員提供了一種使用專家設計經驗的有效途徑。設計模式中運用了面向對象編程語言的重要特性:封裝、繼承、多態,真正領悟設計模式的精髓是可能一個漫長的過程,需要大量實踐經驗的積累。最近看設計模式的書,對于每個模式,用C++寫了個小例子,加深一下理解。
一、設計模式的分類
總體來說設計模式分為三大類
創建型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。
結構型模式,共七種:適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。
行為型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態模式、訪問者模式、中介者模式、解釋器模式。
其實還有兩類:并發型模式和線程池模式。
二、設計模式的六大原則
總原則:開閉原則(Open Close Principle)
開閉原則就是說對擴展開放,對修改關閉。在程序需要進行拓展的時候,不能去修改原有的代碼,而是要擴展原有代碼,實現一個熱插拔的效果。所以一句話概括就是:為了使程序的擴展性好,易于維護和升級。想要達到這樣的效果,我們需要使用接口和抽象類等,后面的具體設計中我們會提到這點。
1、單一職責原則
不要存在多于一個導致類變更的原因,也就是說每個類應該實現單一的職責,如若不然,就應該把類拆分。
2、里氏替換原則(Liskov Substitution Principle)
里氏代換原則(Liskov Substitution Principle LSP)面向對象設計的基本原則之一。里氏代換原則中說,任何基類可以出現的地方,子類一定可以出現。LSP是繼承復用的基石,只有當衍生類可以替換掉基類,軟件單位的功能不受到影響時,基類才能真正被復用,而衍生類也能夠在基類的基礎上增加新的行為。里氏代換原則是對“開-閉”原則的補充。實現“開-閉”原則的關鍵步驟就是抽象化。而基類與子類的繼承關系就是抽象化的具體實現,所以里氏代換原則是對實現抽象化的具體步驟的規范。
歷史替換原則中,子類對父類的方法盡量不要重寫和重載。因為父類代表了定義好的結構,通過這個規范的接口與外界交互,子類不應該隨便破壞它。
3、依賴倒轉原則(Dependence Inversion Principle)
這個是開閉原則的基礎,具體內容:面向接口編程,依賴于抽象而不依賴于具體。寫代碼時用到具體類時,不與具體類交互,而與具體類的上層接口交互。
4、接口隔離原則(Interface Segregation Principle)
這個原則的意思是:每個接口中不存在子類用不到卻必須實現的方法,如果不然,就要將接口拆分。使用多個隔離的接口,比使用單個接口(多個接口方法集合到一個的接口)要好。
5、迪米特法則(最少知道原則)(Demeter Principle)
就是說:一個類對自己依賴的類知道的越少越好。也就是說無論被依賴的類多么復雜,都應該將邏輯封裝在方法的內部,通過public方法提供給外部。這樣當被依賴的類變化時,才能最小的影響該類。
最少知道原則的另一個表達方式是:只與直接的朋友通信。類之間只要有耦合關系,就叫朋友關系。耦合分為依賴、關聯、聚合、組合等。我們稱出現為成員變量、方法參數、方法返回值中的類為直接朋友。局部變量、臨時變量則不是直接的朋友。我們要求陌生的類不要作為局部變量出現在類中。
6、合成復用原則(Composite Reuse Principle)
原則是盡量首先使用合成/聚合的方式,而不是使用繼承。
1.工廠模式
工廠模式屬于創建型模式,大致可以分為三類,簡單工廠模式、工廠方法模式、抽象工廠模式。聽上去差不多,都是工廠模式。下面一個個介紹,首先介紹簡單工廠模式,它的主要特點是需要在工廠類中做判斷,從而創造相應的產品。當增加新的產品時,就需要修改工廠類。有點抽象,舉個例子就明白了。有一家生產處理器核的廠家,它只有一個工廠,能夠生產兩種型號的處理器核。客戶需要什么樣的處理器核,一定要顯示地告訴生產工廠。下面給出一種實現方案。
enum CTYPE {COREA, COREB};
class SingleCore
{
public:
virtual void Show() = 0;
};
//單核A
class SingleCoreA: public SingleCore
{
public:
void Show() { cout< "SingleCore A"<
這樣設計的主要缺點之前也提到過,就是要增加新的核類型時,就需要修改工廠類。這就違反了開放封閉原則:軟件實體(類、模塊、函數)可以擴展,但是不可修改。于是,工廠方法模式出現了。所謂工廠方法模式,是指定義一個用于創建對象的接口,讓子類決定實例化哪一個類。Factory Method使一個類的實例化延遲到其子類。
聽起來很抽象,還是以剛才的例子解釋。這家生產處理器核的產家賺了不少錢,于是決定再開設一個工廠專門用來生產B型號的單核,而原來的工廠專門用來生產A型號的單核。這時,客戶要做的是找好工廠,比如要A型號的核,就找A工廠要;否則找B工廠要,不再需要告訴工廠具體要什么型號的處理器核了。下面給出一個實現方案。
class SingleCore
{
public:
virtual void Show() = 0;
};
//單核A
class SingleCoreA: public SingleCore
{
public:
void Show() { cout< "SingleCore A"<
工廠方法模式也有缺點,每增加一種產品,就需要增加一個對象的工廠。如果這家公司發展迅速,推出了很多新的處理器核,那么就要開設相應的新工廠。在C++實現中,就是要定義一個個的工廠類。顯然,相比簡單工廠模式,工廠方法模式需要更多的類定義。
既然有了簡單工廠模式和工廠方法模式,為什么還要有抽象工廠模式呢?它到底有什么作用呢?還是舉這個例子,這家公司的技術不斷進步,不僅可以生產單核處理器,也能生產多核處理器。現在簡單工廠模式和工廠方法模式都鞭長莫及。抽象工廠模式登場了。它的定義為提供一個創建一系列相關或相互依賴對象的接口,而無需指定它們具體的類。具體這樣應用,這家公司還是開設兩個工廠,一個專門用來生產A型號的單核多核處理器,而另一個工廠專門用來生產B型號的單核多核處理器,下面給出實現的代碼。
//單核
class SingleCore
{
public:
virtual void Show() = 0;
};
class SingleCoreA: public SingleCore
{
public:
void Show() { cout< "Single Core A"<
至此,工廠模式介紹完了。給出三種工廠模式的UML圖,加深印象。
簡單工廠模式的UML圖:
工廠方法的UML圖:
抽象工廠模式的UML圖:
2.策略模式
策略模式是指定義一系列的算法,把它們一個個封裝起來,并且使它們可相互替換。本模式使得算法可獨立于使用它的客戶而變化。也就是說這些算法所完成的功能一樣,對外的接口一樣,只是各自實現上存在差異。用策略模式來封裝算法,效果比較好。下面以高速緩存(Cache)的替換算法為例,實現策略模式。
什么是Cache的替換算法呢?簡單解釋一下, 當發生Cache缺失時,Cache控制器必須選擇Cache中的一行,并用欲獲得的數據來替換它。所采用的選擇策略就是Cache的替換算法。下面給出相應的UML圖。
ReplaceAlgorithm是一個抽象類,定義了算法的接口,有三個類繼承自這個抽象類,也就是具體的算法實現。Cache類中需要使用替換算法,因此維護了一個 ReplaceAlgorithm的對象。這個UML圖的結構就是策略模式的典型結構。下面根據UML圖,給出相應的實現。
首先給出替換算法的定義。
//抽象接口
class ReplaceAlgorithm
{
public:
virtual void Replace() = 0;
};
//三種具體的替換算法
class LRU_ReplaceAlgorithm : public ReplaceAlgorithm
{
public:
void Replace() { cout< "Least Recently Used replace algorithm"<
接著給出Cache的定義,這里很關鍵,Cache的實現方式直接影響了客戶的使用方式,其關鍵在于如何指定替換算法。
方式一:直接通過參數指定,傳入一個特定算法的指針。
//Cache需要用到替換算法
class Cache
{
private:
ReplaceAlgorithm *m_ra;
public:
Cache(ReplaceAlgorithm *ra) { m_ra = ra; }
~Cache() { delete m_ra; }
void Replace() { m_ra- >Replace(); }
};
如果用這種方式,客戶就需要知道這些算法的具體定義。只能以下面這種方式使用,可以看到暴露了太多的細節。
int main()
{
Cache cache(new LRU_ReplaceAlgorithm()); //暴露了算法的定義
cache.Replace();
return 0;
}
方式二:也是直接通過參數指定,只不過不是傳入指針,而是一個標簽。這樣客戶只要知道算法的相應標簽即可,而不需要知道算法的具體定義。
//Cache需要用到替換算法
enum RA {LRU, FIFO, RANDOM}; //標簽
class Cache
{
private:
ReplaceAlgorithm *m_ra;
public:
Cache(enum RA ra)
{
if(ra == LRU)
m_ra = new LRU_ReplaceAlgorithm();
else if(ra == FIFO)
m_ra = new FIFO_ReplaceAlgorithm();
else if(ra == RANDOM)
m_ra = new Random_ReplaceAlgorithm();
else
m_ra = NULL;
}
~Cache() { delete m_ra; }
void Replace() { m_ra- >Replace(); }
};
相比方式一,這種方式用起來方便多了。其實這種方式將簡單工廠模式與策略模式結合在一起,算法的定義使用了策略模式,而Cache的定義其實使用了簡單工廠模式。
int main()
{
Cache cache(LRU); //指定標簽即可
cache.Replace();
return 0;
}
上面兩種方式,構造函數都需要形參。構造函數是否可以不用參數呢?下面給出第三種實現方式。
方式三:利用模板實現。算法通過模板的實參指定。當然了,還是使用了參數,只不過不是構造函數的參數。在策略模式中,參數的傳遞難以避免,客戶必須指定某種算法。
//Cache需要用到替換算法
template < class RA >
class Cache
{
private:
RA m_ra;
public:
Cache() { }
~Cache() { }
void Replace() { m_ra.Replace(); }
};
使用方式如下:
int main()
{
Cache< Random_ReplaceAlgorithm > cache; //模板實參
cache.Replace();
return 0;
}
3.適配器模式
DP上的定義:適配器模式將一個類的接口轉換成客戶希望的另外一個接口,使得原本由于接口不兼容而不能一起工作的那些類可以一起工作。它包括類適配器和對象適配器,本文針對的是對象適配器。舉個例子,在STL中就用到了適配器模式。STL實現了一種數據結構,稱為雙端隊列(deque),支持前后兩段的插入與刪除。STL實現棧和隊列時,沒有從頭開始定義它們,而是直接使用雙端隊列實現的。這里雙端隊列就扮演了適配器的角色。隊列用到了它的后端插入,前端刪除。而棧用到了它的后端插入,后端刪除。假設棧和隊列都是一種順序容器,有兩種操作:壓入和彈出。下面給出相應的UML圖,與DP上的圖差不多。
根據上面的UML圖,很容易給出實現。
//雙端隊列
class Deque
{
public:
void push_back(int x) { cout< "Deque push_back"<
使用方式如下:
int main()
{
Sequence *s1 = new Stack();
Sequence *s2 = new Queue();
s1- >push(1); s1- >pop();
s2- >push(1); s2- >pop();
delete s1; delete s2;
return 0;
}
4.單例模式
單例的一般實現比較簡單,下面是代碼和UML圖。由于構造函數是私有的,因此無法通過構造函數實例化,唯一的方法就是通過調用靜態函數GetInstance。
UML圖:
代碼:
//Singleton.h
class Singleton
{
public:
static Singleton* GetInstance();
private:
Singleton() {}
static Singleton *singleton;
};
//Singleton.cpp
Singleton* Singleton::singleton = NULL;
Singleton* Singleton::GetInstance()
{
if(singleton == NULL)
singleton = new Singleton();
return singleton;
}
這里只有一個類,如何實現Singleton類的子類呢?也就說Singleton有很多子類,在一種應用中,只選擇其中的一個。最容易就是在GetInstance函數中做判斷,比如可以傳遞一個字符串,根據字符串的內容創建相應的子類實例。這也是DP書上的一種解法,書上給的代碼不全。這里重新實現了一下,發現不是想象中的那么簡單,最后實現的版本看上去很怪異。在VS2008下測試通過。
//Singleton.h
#pragma once
#include < iostream >
using namespace std;
class Singleton
{
public:
static Singleton* GetInstance(const char* name);
virtual void Show() {}
protected: //必須為保護,如果是私有屬性,子類無法訪問父類的構造函數
Singleton() {}
private:
static Singleton *singleton; //唯一實例的指針
};
//Singleton.cpp
#include "Singleton.h"
#include "SingletonA.h"
#include "SingletonB.h"
Singleton* Singleton::singleton = NULL;
Singleton* Singleton::GetInstance(const char* name)
{
if(singleton == NULL)
{
if(strcmp(name, "SingletonA") == 0)
singleton = new SingletonA();
else if(strcmp(name,"SingletonB") == 0)
singleton = new SingletonB();
else
singleton = new Singleton();
}
return singleton;
}
//SingletonA.h
#pragma once
#include "Singleton.h"
class SingletonA: public Singleton
{
friend class Singleton; //必須為友元類,否則父類無法訪問子類的構造函數
public:
void Show() { cout< "SingletonA"<
#include "Singleton.h"
int main()
{
Singleton *st = Singleton::GetInstance("SingletonA");
st- >Show();
return 0;
}
上面代碼有一個地方很詭異,父類為子類的友元,如果不是友元,函數GetInstance會報錯,意思就是無法調用SingletonA和SIngletonB的構造函數。父類中調用子類的構造函數,我還是第一次碰到。當然了把SingletonA和SIngletonB的屬性設為public,GetInstance函數就不會報錯了,但是這樣外界就可以定義這些類的對象,違反了單例模式。
看似奇怪,其實也容易解釋。在父類中構建子類的對象,相當于是外界調用子類的構造函數,因此當子類構造函數的屬性為私有或保護時,父類無法訪問。為共有時,外界就可以訪問子類的構造函數了,此時父類當然也能訪問了。只不過為了保證單例模式,所以子類的構造函數不能為共有,但是又希望在父類中構造子類的對象,即需要調用子類的構造函數,這里沒有辦法才出此下策:將父類聲明為子類的友元類。
5.原型模式、模板方法模式
DP書上的定義為:用原型實例指定創建對象的種類,并且通過拷貝這些原型創建新的對象。其中有一個詞很重要,那就是拷貝。可以說,拷貝是原型模式的精髓所在。舉個現實中的例子來介紹原型模式。找工作的時候,我們需要準備簡歷。假設沒有打印設備,因此需手寫簡歷,這些簡歷的內容都是一樣的。這樣有個缺陷,如果要修改簡歷中的某項,那么所有已寫好的簡歷都要修改,工作量很大。隨著科技的進步,出現了打印設備。我們只需手寫一份,然后利用打印設備復印多份即可。如果要修改簡歷中的某項,那么修改原始的版本就可以了,然后再復印。原始的那份手寫稿相當于是一個原型,有了它,就可以通過復印(拷貝)創造出更多的新簡歷。這就是原型模式的基本思想。下面給出原型模式的UML圖,以剛才那個例子為實例。
原型模式實現的關鍵就是實現Clone函數,對于C++來說,其實就是拷貝構造函數,需實現深拷貝,下面給出一種實現。
//父類
class Resume
{
protected:
char *name;
public:
Resume() {}
virtual ~Resume() {}
virtual Resume* Clone() { return NULL; }
virtual void Set(char *n) {}
virtual void Show() {}
};
class ResumeA : public Resume
{
public:
ResumeA(const char *str); //構造函數
ResumeA(const ResumeA &r); //拷貝構造函數
~ResumeA(); //析構函數
ResumeA* Clone(); //克隆,關鍵所在
void Show(); //顯示內容
};
ResumeA::ResumeA(const char *str)
{
if(str == NULL) {
name = new char[1];
name[0] = '?';
}
else {
name = new char[strlen(str)+1];
strcpy(name, str);
}
}
ResumeA::~ResumeA() { delete [] name;}
ResumeA::ResumeA(const ResumeA &r) {
name = new char[strlen(r.name)+1];
strcpy(name, r.name);
}
ResumeA* ResumeA::Clone() {
return new ResumeA(*this);
}
void ResumeA::Show() {
cout< "ResumeA name : "<
這里只給出了ResumeA的實現,ResumeB的實現類似。使用的方式如下:
int main()
{
Resume *r1 = new ResumeA("A");
Resume *r2 = new ResumeB("B");
Resume *r3 = r1- >Clone();
Resume *r4 = r2- >Clone();
r1- >Show(); r2- >Show();
//刪除r1,r2
delete r1; delete r2;
r1 = r2 = NULL;
//深拷貝所以對r3,r4無影響
r3- >Show(); r4- >Show();
delete r3; delete r4;
r3 = r4 = NULL;
}
最近有個招聘會,可以帶上簡歷去應聘了。但是,其中有一家公司不接受簡歷,而是給應聘者發了一張簡歷表,上面有基本信息、教育背景、工作經歷等欄,讓應聘者按照要求填寫完整。每個人拿到這份表格后,就開始填寫。如果用程序實現這個過程,該如何做呢?一種方案就是用模板方法模式:定義一個操作中的算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。我們的例子中,操作就是填寫簡歷這一過程,我們可以在父類中定義操作的算法骨架,而具體的實現由子類完成。下面給出它的UML圖。
其中FillResume() 定義了操作的骨架,依次調用子類實現的函數。相當于每個人填寫簡歷的實際過程。接著給出相應的C++代碼。
//簡歷
class Resume
{
protected: //保護成員
virtual void SetPersonalInfo() {}
virtual void SetEducation() {}
virtual void SetWorkExp() {}
public:
void FillResume()
{
SetPersonalInfo();
SetEducation();
SetWorkExp();
}
};
class ResumeA: public Resume
{
protected:
void SetPersonalInfo() { cout< "A's PersonalInfo"<
使用方式如下:
int main()
{
Resume *r1;
r1 = new ResumeA();
r1- >FillResume();
delete r1;
r1 = new ResumeB();
r1- >FillResume();
delete r1;
r1 = NULL;
return 0;
}
6.建造者模式
建造者模式的定義將一個復雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示(DP)。《大話設計模式》舉了一個很好的例子——建造小人,一共需建造6個部分,頭部、身體、左右手、左右腳。與工廠模式不同,建造者模式是在導向者的控制下一步一步構造產品的。建造小人就是在控制下一步步構造出來的。創建者模式可以能更精細的控制構建過程,從而能更精細的控制所得產品的內部結構。下面給出建造者模式的UML圖,以建造小人為實例。
對于客戶來說,只需知道導向者就可以了,通過導向者,客戶就能構造復雜的對象,而不需要知道具體的構造過程。下面給出小人例子的代碼實現。
class Builder
{
public:
virtual void BuildHead() {}
virtual void BuildBody() {}
virtual void BuildLeftArm(){}
virtual void BuildRightArm() {}
virtual void BuildLeftLeg() {}
virtual void BuildRightLeg() {}
};
//構造瘦人
class ThinBuilder : public Builder
{
public:
void BuildHead() { cout< "build thin body"<
客戶的使用方式:
int main() int main()
{
FatBuilder thin;
Director director(&thin);
director.Create();
return 0;
}
{
FatBuilder thin;
Director director(&thin);
director.Create();
return 0;
}
7.外觀模式、組合模式
外觀模式應該是用的很多的一種模式,特別是當一個系統很復雜時,系統提供給客戶的是一個簡單的對外接口,而把里面復雜的結構都封裝了起來。客戶只需使用這些簡單接口就能使用這個系統,而不需要關注內部復雜的結構。DP一書的定義:為子系統中的一組接口提供一個一致的界面, 外觀模式定義了一個高層接口,這個接口使得這一子系統更加容易使用。舉個編譯器的例子,假設編譯一個程序需要經過四個步驟:詞法分析、語法分析、中間代碼生成、機器碼生成。學過編譯都知道,每一步都很復雜。對于編譯器這個系統,就可以使用外觀模式。可以定義一個高層接口,比如名為Compiler的類,里面有一個名為Run的函數。客戶只需調用這個函數就可以編譯程序,至于Run函數內部的具體操作,客戶無需知道。下面給出UML圖,以編譯器為實例。
相應的代碼實現為:
class Scanner
{
public:
void Scan() { cout< "詞法分析"<
客戶使用方式:
int main()
{
Compiler compiler;
Console Compiler();
return 0;
}
這就是外觀模式,它有幾個特點(摘自DP一書),(1)它對客戶屏蔽子系統組件,因而減少了客戶處理的對象的數目并使得子系統使用起來更加方便。(2)它實現了子系統與客戶之間的松耦合關系,而子系統內部的功能組件往往是緊耦合的。(3)如果應用需要,它并不限制它們使用子系統類。
結合上面編譯器這個例子,進一步說明。對于(1),編譯器類對客戶屏蔽了子系統組件,客戶只需處理編譯器的對象就可以方便的使用子系統。對于(2),子系統的變化,不會影響到客戶的使用,體現了子系統與客戶的松耦合關系。對于(3),如果客戶希望使用詞法分析器,只需定義詞法分析的類對象即可,并不受到限制。
外觀模式在構建大型系統時非常有用。接下來介紹另一種模式,稱為組合模式。感覺有點像外觀模式,剛才我們實現外觀模式時,在Compiler這個類中包含了多個類的對象,就像把這些類組合在了一起。組合模式是不是這個意思,有點相似,其實不然。
DP書上給出的定義:將對象組合成樹形結構以表示“部分-整體”的層次結構。組合使得用戶對單個對象和組合對象的使用具有一致性。注意兩個字“樹形”。這種樹形結構在現實生活中隨處可見,比如一個集團公司,它有一個母公司,下設很多家子公司。不管是母公司還是子公司,都有各自直屬的財務部、人力資源部、銷售部等。對于母公司來說,不論是子公司,還是直屬的財務部、人力資源部,都是它的部門。整個公司的部門拓撲圖就是一個樹形結構。
下面給出組合模式的UML圖。從圖中可以看到,FinanceDepartment、HRDepartment兩個類作為葉結點,因此沒有定義添加函數。而ConcreteCompany類可以作為中間結點,所以可以有添加函數。那么怎么添加呢?這個類中定義了一個鏈表,用來放添加的元素。
相應的代碼實現為:
class Company
{
public:
Company(string name) { m_name = name; }
virtual ~Company(){}
virtual void Add(Company *pCom){}
virtual void Show(int depth) {}
protected:
string m_name;
};
//具體公司
class ConcreteCompany : public Company
{
public:
ConcreteCompany(string name): Company(name) {}
virtual ~ConcreteCompany() {}
void Add(Company *pCom) { m_listCompany.push_back(pCom); } //位于樹的中間,可以增加子樹
void Show(int depth)
{
for(int i = 0;i < depth; i++)
cout< "-";
cout<
客戶使用方式:
int main()
{
Company *root = new ConcreteCompany("總公司");
Company *leaf1=new FinanceDepartment("財務部");
Company *leaf2=new HRDepartment("人力資源部");
root- >Add(leaf1);
root- >Add(leaf2);
//分公司A
Company *mid1 = new ConcreteCompany("分公司A");
Company *leaf3=new FinanceDepartment("財務部");
Company *leaf4=new HRDepartment("人力資源部");
mid1- >Add(leaf3);
mid1- >Add(leaf4);
root- >Add(mid1);
//分公司B
Company *mid2=new ConcreteCompany("分公司B");
FinanceDepartment *leaf5=new FinanceDepartment("財務部");
HRDepartment *leaf6=new HRDepartment("人力資源部");
mid2- >Add(leaf5);
mid2- >Add(leaf6);
root- >Add(mid2);
root- >Show(0);
delete leaf1; delete leaf2;
delete leaf3; delete leaf4;
delete leaf5; delete leaf6;
delete mid1; delete mid2;
delete root;
return 0;
}
上面的實現方式有缺點,就是內存的釋放不好,需要客戶自己動手,非常不方便。有待改進,比較好的做法是讓ConcreteCompany類來釋放。因為所有的指針都是存在ConcreteCompany類的鏈表中。C++的麻煩,沒有垃圾回收機制。
8.代理模式
[DP]上的定義:為其他對象提供一種代理以控制對這個對象的訪問。有四種常用的情況:(1)遠程代理,(2)虛代理,(3)保護代理,(4)智能引用。本文主要介紹虛代理和智能引用兩種情況。
考慮一個可以在文檔中嵌入圖形對象的文檔編輯器。有些圖形對象的創建開銷很大。但是打開文檔必須很迅速,因此我們在打開文檔時應避免一次性創建所有開銷很大的對象。這里就可以運用代理模式,在打開文檔時,并不打開圖形對象,而是打開圖形對象的代理以替代真實的圖形。待到真正需要打開圖形時,仍由代理負責打開。這是[DP]一書上的給的例子。下面給出代理模式的UML圖。
簡單實現如下:
class Image
{
public:
Image(string name): m_imageName(name) {}
virtual ~Image() {}
virtual void Show() {}
protected:
string m_imageName;
};
class BigImage: public Image
{
public:
BigImage(string name):Image(name) {}
~BigImage() {}
void Show() { cout< "Show big image : "<
客戶調用:
int main()
{
Image *image = new BigImageProxy("proxy.jpg"); //代理
image- >Show(); //需要時由代理負責打開
delete image;
return 0;
}
在這個例子屬于虛代理的情況,下面給兩個智能引用的例子。一個是C++中的auto_ptr,另一個是smart_ptr。自己實現了一下。先給出auto_ptr的代碼實現:
template< class T >
class auto_ptr {
public:
explicit auto_ptr(T *p = 0): pointee(p) {}
auto_ptr(auto_ptr< T >& rhs): pointee(rhs.release()) {}
~auto_ptr() { delete pointee; }
auto_ptr< T >& operator=(auto_ptr< T >& rhs)
{
if (this != &rhs) reset(rhs.release());
return *this;
}
T& operator*() const { return *pointee; }
T* operator- >() const { return pointee; }
T* get() const { return pointee; }
T* release()
{
T *oldPointee = pointee;
pointee = 0;
return oldPointee;
}
void reset(T *p = 0)
{
if (pointee != p) {
delete pointee;
pointee = p;
}
}
private:
T *pointee;
};
閱讀上面的代碼,我們可以發現 auto_ptr 類就是一個代理,客戶只需操作auto_prt的對象,而不需要與被代理的指針pointee打交道。auto_ptr 的好處在于為動態分配的對象提供異常安全。因為它用一個對象存儲需要被自動釋放的資源,然后依靠對象的析構函數來釋放資源。這樣客戶就不需要關注資源的釋放,由auto_ptr 對象自動完成。實現中的一個關鍵就是重載了解引用操作符和箭頭操作符,從而使得auto_ptr的使用與真實指針類似。
我們知道C++中沒有垃圾回收機制,可以通過智能指針來彌補,下面給出智能指針的一種實現,采用了引用計數的策略。
template < typename T >
class smart_ptr
{
public:
smart_ptr(T *p = 0): pointee(p), count(new size_t(1)) { } //初始的計數值為1
smart_ptr(const smart_ptr &rhs): pointee(rhs.pointee), count(rhs.count) { ++*count; } //拷貝構造函數,計數加1
~smart_ptr() { decr_count(); } //析構,計數減1,減到0時進行垃圾回收,即釋放空間
smart_ptr& operator= (const smart_ptr& rhs) //重載賦值操作符
{
//給自身賦值也對,因為如果自身賦值,計數器先減1,再加1,并未發生改變
++*count;
decr_count();
pointee = rhs.pointee;
count = rhs.count;
return *this;
}
//重載箭頭操作符和解引用操作符,未提供指針的檢查
T *operator- >() { return pointee; }
const T *operator- >() const { return pointee; }
T &operator*() { return *pointee; }
const T &operator*() const { return *pointee; }
size_t get_refcount() { return *count; } //獲得引用計數器值
private:
T *pointee; //實際指針,被代理
size_t *count; //引用計數器
void decr_count() //計數器減1
{
if(--*count == 0)
{
delete pointee;
delete count;
}
}
};
9.享元模式
舉個圍棋的例子,圍棋的棋盤共有361格,即可放361個棋子。現在要實現一個圍棋程序,該怎么辦呢?首先要考慮的是棋子棋盤的實現,可以定義一個棋子的類,成員變量包括棋子的顏色、形狀、位置等信息,另外再定義一個棋盤的類,成員變量中有個容器,用于存放棋子的對象。下面給出代碼表示:
棋子的定義,當然棋子的屬性除了顏色和位置,還有其他的,這里略去。這兩個屬性足以說明問題。
//棋子顏色
enum PieceColor {BLACK, WHITE};
//棋子位置
struct PiecePos
{
int x;
int y;
PiecePos(int a, int b): x(a), y(b) {}
};
//棋子定義
class Piece
{
protected:
PieceColor m_color; //顏色
PiecePos m_pos; //位置
public:
Piece(PieceColor color, PiecePos pos): m_color(color), m_pos(pos) {}
~Piece() {}
virtual void Draw() {}
};
class BlackPiece: public Piece
{
public:
BlackPiece(PieceColor color, PiecePos pos): Piece(color, pos) {}
~BlackPiece() {}
void Draw() { cout< "繪制一顆黑棋"<
棋盤的定義:
class PieceBoard
{
private:
vector< Piece* > m_vecPiece; //棋盤上已有的棋子
string m_blackName; //黑方名稱
string m_whiteName; //白方名稱
public:
PieceBoard(string black, string white): m_blackName(black), m_whiteName(white){}
~PieceBoard() { Clear(); }
void SetPiece(PieceColor color, PiecePos pos) //一步棋,在棋盤上放一顆棋子
{
Piece * piece = NULL;
if(color == BLACK) //黑方下的
{
piece = new BlackPiece(color, pos); //獲取一顆黑棋
cout<
客戶的使用方式如下:
int main()
{
PieceBoard pieceBoard("A","B");
pieceBoard.SetPiece(BLACK, PiecePos(4, 4));
pieceBoard.SetPiece(WHITE, PiecePos(4, 16));
pieceBoard.SetPiece(BLACK, PiecePos(16, 4));
pieceBoard.SetPiece(WHITE, PiecePos(16, 16));
}
可以發現,棋盤的容器中存放了已下的棋子,而每個棋子包含棋子的所有屬性。一盤棋往往需要含上百顆棋子,采用上面這種實現,占用的空間太大了。如何改進呢?用享元模式。其定義為:運用共享技術有效地支持大量細粒度的對象。
在圍棋中,棋子就是大量細粒度的對象。其屬性有內在的,比如顏色、形狀等,也有外在的,比如在棋盤上的位置。內在的屬性是可以共享的,區分在于外在屬性。因此,可以這樣設計,只需定義兩個棋子的對象,一顆黑棋和一顆白棋,這兩個對象含棋子的內在屬性;棋子的外在屬性,即在棋盤上的位置可以提取出來,存放在單獨的容器中。相比之前的方案,現在容器中僅僅存放了位置屬性,而原來則是棋子對象。顯然,現在的方案大大減少了對于空間的需求。
關注PieceBoard 的容器,之前是vector m_vecPiece,現在是vector m_vecPos。這里是關鍵。
棋子的新定義,只包含內在屬性:
//棋子顏色
enum PieceColor {BLACK, WHITE};
//棋子位置
struct PiecePos
{
int x;
int y;
PiecePos(int a, int b): x(a), y(b) {}
};
//棋子定義
class Piece
{
protected:
PieceColor m_color; //顏色
public:
Piece(PieceColor color): m_color(color) {}
~Piece() {}
virtual void Draw() {}
};
class BlackPiece: public Piece
{
public:
BlackPiece(PieceColor color): Piece(color) {}
~BlackPiece() {}
void Draw() { cout< "繪制一顆黑棋n"; }
};
class WhitePiece: public Piece
{
public:
WhitePiece(PieceColor color): Piece(color) {}
~WhitePiece() {}
void Draw() { cout< "繪制一顆白棋n";}
};
相應棋盤的定義為:
class PieceBoard
{
private:
vector< PiecePos > m_vecPos; //存放棋子的位置
Piece *m_blackPiece; //黑棋棋子
Piece *m_whitePiece; //白棋棋子
string m_blackName;
string m_whiteName;
public:
PieceBoard(string black, string white): m_blackName(black), m_whiteName(white)
{
m_blackPiece = NULL;
m_whitePiece = NULL;
}
~PieceBoard() { delete m_blackPiece; delete m_whitePiece;}
void SetPiece(PieceColor color, PiecePos pos)
{
if(color == BLACK)
{
if(m_blackPiece == NULL) //只有一顆黑棋
m_blackPiece = new BlackPiece(color);
cout<
客戶的使用方式一樣,這里不重復給出,現在給出享元模式的UML圖,以圍棋為例。棋盤中含兩個共享的對象,黑棋子和白棋子,所有棋子的外在屬性都存放在單獨的容器中。
10、橋接模式
[DP]書上定義:將抽象部分與它的實現部分分離,使它們都可以獨立地變化。考慮裝操作系統,有多種配置的計算機,同樣也有多款操作系統。如何運用橋接模式呢?可以將操作系統和計算機分別抽象出來,讓它們各自發展,減少它們的耦合度。當然了,兩者之間有標準的接口。這樣設計,不論是對于計算機,還是操作系統都是非常有利的。下面給出這種設計的UML圖,其實就是橋接模式的UML圖。
給出C++的一種實現:
//操作系統
class OS
{
public:
virtual void InstallOS_Imp() {}
};
class WindowOS: public OS
{
public:
void InstallOS_Imp() { cout< "安裝Window操作系統"<
客戶使用方式:
int main()
{
OS *os1 = new WindowOS();
OS *os2 = new LinuxOS();
Computer *computer1 = new AppleComputer();
computer1- >InstallOS(os1);
computer1- >InstallOS(os2);
}
11.裝飾模式
裝飾模式:動態地給一個對象添加一些額外的職責。就增加功能來說,裝飾模式相比生成子類更為靈活。有時我們希望給某個對象而不是整個類添加一些功能。比如有一個手機,允許你為手機添加特性,比如增加掛件、屏幕貼膜等。一種靈活的設計方式是,將手機嵌入到另一對象中,由這個對象完成特性的添加,我們稱這個嵌入的對象為裝飾。這個裝飾與它所裝飾的組件接口一致,因此它對使用該組件的客戶透明。下面給出裝飾模式的UML圖。
在這種設計中,手機的裝飾功能被獨立出來,可以單獨發展,進而簡化了具體手機類的設計。下面給出Phone類的實現:
//公共抽象類
class Phone
{
public:
Phone() {}
virtual ~Phone() {}
virtual void ShowDecorate() {}
};
具體的手機類的定義:
//具體的手機類
class iPhone : public Phone
{
private:
string m_name; //手機名稱
public:
iPhone(string name): m_name(name){}
~iPhone() {}
void ShowDecorate() { cout<
裝飾類的實現:
//裝飾類
class DecoratorPhone : public Phone
{
private:
Phone *m_phone; //要裝飾的手機
public:
DecoratorPhone(Phone *phone): m_phone(phone) {}
virtual void ShowDecorate() { m_phone- >ShowDecorate(); }
};
//具體的裝飾類
class DecoratorPhoneA : public DecoratorPhone
{
public:
DecoratorPhoneA(Phone *phone) : DecoratorPhone(phone) {}
void ShowDecorate() { DecoratorPhone::ShowDecorate(); AddDecorate(); }
private:
void AddDecorate() { cout< "增加掛件"<
客戶使用方式:
int main()
{
Phone *iphone = new NokiaPhone("6300");
Phone *dpa = new DecoratorPhoneA(iphone); //裝飾,增加掛件
Phone *dpb = new DecoratorPhoneB(dpa); //裝飾,屏幕貼膜
dpb- >ShowDecorate();
delete dpa;
delete dpb;
delete iphone;
return 0;
}
裝飾模式提供了更加靈活的向對象添加職責的方式。可以用添加和分離的方法,用裝飾在運行時刻增加和刪除職責。裝飾模式提供了一種“即用即付”的方法來添加職責。它并不試圖在一個復雜的可定制的類中支持所有可預見的特征,相反,你可以定義一個簡單的類,并且用裝飾類給它逐漸地添加功能。可以從簡單的部件組合出復雜的功能
12.備忘錄模式
備忘錄模式:在不破壞封裝性的前提下,捕獲一個對象的內部狀態,并在該對象之外保存這個狀態。這樣以后就可將該對象恢復到原先保存的狀態[DP]。舉個簡單的例子,我們玩游戲時都會保存進度,所保存的進度以文件的形式存在。這樣下次就可以繼續玩,而不用從頭開始。這里的進度其實就是游戲的內部狀態,而這里的文件相當于是在游戲之外保存狀態。這樣,下次就可以從文件中讀入保存的進度,從而恢復到原來的狀態。這就是備忘錄模式。
給出備忘錄模式的UML圖,以保存游戲的進度為例。
Memento類定義了內部的狀態,而Caretake類是一個保存進度的管理者,GameRole類是游戲角色類。可以看到GameRole的對象依賴于Memento對象,而與Caretake對象無關。下面給出一個簡單的是實現。
//需保存的信息
class Memento
{
public:
int m_vitality; //生命值
int m_attack; //進攻值
int m_defense; //防守值
public:
Memento(int vitality, int attack, int defense):
m_vitality(vitality),m_attack(attack),m_defense(defense){}
Memento& operator=(const Memento &memento)
{
m_vitality = memento.m_vitality;
m_attack = memento.m_attack;
m_defense = memento.m_defense;
return *this;
}
};
//游戲角色
class GameRole
{
private:
int m_vitality;
int m_attack;
int m_defense;
public:
GameRole(): m_vitality(100),m_attack(100),m_defense(100) {}
Memento Save() //保存進度,只與Memento對象交互,并不牽涉到Caretake
{
Memento memento(m_vitality, m_attack, m_defense);
return memento;
}
void Load(Memento memento) //載入進度,只與Memento對象交互,并不牽涉到Caretake
{
m_vitality = memento.m_vitality;
m_attack = memento.m_attack;
m_defense = memento.m_defense;
}
void Show() { cout< "vitality : "< < m_vitality< ", attack : "< < m_attack< ", defense : "< < m_defense<
客戶使用方式:
//測試案例
int main()
{
Caretake caretake;
GameRole role;
role.Show(); //初始值
caretake.Save(role.Save()); //保存狀態
role.Attack();
role.Show(); //進攻后
role.Load(caretake.Load(0)); //載入狀態
role.Show(); //恢復到狀態0
return 0;
}
13.中介者模式
中介者模式:用一個中介對象來封裝一系列的對象交互。中介者使各對象不需要顯式地相互引用,從而使其耦合松散,而且可以獨立地改變它們之間的交互。中介者模式的例子很多,大到聯合國安理會,小到房屋中介,都扮演了中間者的角色,協調各方利益。
本文就以租房為例子,如果沒有房屋中介,那么房客要自己找房東,而房東也要自己找房客,非常不方便。有了房屋中介機構就方便了,房東可以把要出租的房屋信息放到中介機構,而房客可以去中介機構咨詢。在軟件中,就是多個對象之間需要通信,如果沒有中介,對象就需要知道其他對象,最壞情況下,可能需要知道所有其他對象,而有了中介對象就方便多了,對象只需與中介對象通信,而不用知道其他的對象。這就是中介者模式,下面以租房為例,給出中介者模式的UML圖。
實現不難,下面給出C++的實現:
class Mediator;
//抽象人
class Person
{
protected:
Mediator *m_mediator; //中介
public:
virtual void SetMediator(Mediator *mediator){} //設置中介
virtual void SendMessage(string message) {} //向中介發送信息
virtual void GetMessage(string message) {} //從中介獲取信息
};
//抽象中介機構
class Mediator
{
public:
virtual void Send(string message, Person *person) {}
virtual void SetA(Person *A) {} //設置其中一方
virtual void SetB(Person *B) {}
};
//租房者
class Renter: public Person
{
public:
void SetMediator(Mediator *mediator) { m_mediator = mediator; }
void SendMessage(string message) { m_mediator- >Send(message, this); }
void GetMessage(string message) { cout< "租房者收到信息"<
客戶使用方式如下:
//測試案例
int main()
{
Mediator *mediator = new HouseMediator();
Person *person1 = new Renter(); //租房者
Person *person2 = new Landlord(); //房東
mediator- >SetA(person1);
mediator- >SetB(person2);
person1- >SetMediator(mediator);
person2- >SetMediator(mediator);
person1- >SendMessage("我想在南京路附近租套房子,價格800元一個月n");
person2- >SendMessage("出租房子:南京路100號,70平米,1000元一個月n");
delete person1; delete person2; delete mediator;
return 0;
}
14.職責鏈模式
職責鏈模式:使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關系。將這些對象連成一條鏈,并沿著這條鏈傳遞該請求,直到有一個對象處理它為止。其思想很簡單,考慮員工要求加薪。公司的管理者一共有三級,總經理、總監、經理,如果一個員工要求加薪,應該向主管的經理申請,如果加薪的數量在經理的職權內,那么經理可以直接批準,否則將申請上交給總監。總監的處理方式也一樣,總經理可以處理所有請求。這就是典型的職責鏈模式,請求的處理形成了一條鏈,直到有一個對象處理請求。給出這個例子的UML圖。
代碼的實現比較簡單,如下所示:
//抽象管理者
class Manager
{
protected:
Manager *m_manager;
string m_name;
public:
Manager(Manager *manager, string name):m_manager(manager), m_name(name){}
virtual void DealWithRequest(string name, int num) {}
};
//經理
class CommonManager: public Manager
{
public:
CommonManager(Manager *manager, string name):Manager(manager,name) {}
void DealWithRequest(string name, int num)
{
if(num < 500) //經理職權之內
{
cout< "經理"<
客戶調用方式為:
//測試案例
int main()
{
Manager *general = new GeneralManager(NULL, "A"); //設置上級,總經理沒有上級
Manager *majordomo = new Majordomo(general, "B"); //設置上級
Manager *common = new CommonManager(majordomo, "C"); //設置上級
common- >DealWithRequest("D",300); //員工D要求加薪
common- >DealWithRequest("E", 600);
common- >DealWithRequest("F", 1000);
delete common; delete majordomo; delete general;
return 0;
}
15.觀察者模式
觀察者模式:定義對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴于它的對象都得到通知并被自動更新。它還有兩個別名,依賴(Dependents),發布-訂閱(Publish-Subsrcibe)。可以舉個博客訂閱的例子,當博主發表新文章的時候,即博主狀態發生了改變,那些訂閱的讀者就會收到通知,然后進行相應的動作,比如去看文章,或者收藏起來。博主與讀者之間存在種一對多的依賴關系。下面給出相應的UML圖設計。
可以看到博客類中有一個觀察者鏈表(即訂閱者),當博客的狀態發生變化時,通過Notify成員函數通知所有的觀察者,告訴他們博客的狀態更新了。而觀察者通過Update成員函數獲取博客的狀態信息。代碼實現不難,下面給出C++的一種實現。
//觀察者
class Observer
{
public:
Observer() {}
virtual ~Observer() {}
virtual void Update() {}
};
//博客
class Blog
{
public:
Blog() {}
virtual ~Blog() {}
void Attach(Observer *observer) { m_observers.push_back(observer); } //添加觀察者
void Remove(Observer *observer) { m_observers.remove(observer); } //移除觀察者
void Notify() //通知觀察者
{
list< Observer* >::iterator iter = m_observers.begin();
for(; iter != m_observers.end(); iter++)
(*iter)- >Update();
}
virtual void SetStatus(string s) { m_status = s; } //設置狀態
virtual string GetStatus() { return m_status; } //獲得狀態
private:
list< Observer* > m_observers; //觀察者鏈表
protected:
string m_status; //狀態
};
以上是觀察者和博客的基類,定義了通用接口。博客類主要完成觀察者的添加、移除、通知操作,設置和獲得狀態僅僅是一個默認實現。下面給出它們相應的子類實現。
//具體博客類
class BlogCSDN : public Blog
{
private:
string m_name; //博主名稱
public:
BlogCSDN(string name): m_name(name) {}
~BlogCSDN() {}
void SetStatus(string s) { m_status = "CSDN通知 : " + m_name + s; } //具體設置狀態信息
string GetStatus() { return m_status; }
};
//具體觀察者
class ObserverBlog : public Observer
{
private:
string m_name; //觀察者名稱
Blog *m_blog; //觀察的博客,當然以鏈表形式更好,就可以觀察多個博客
public:
ObserverBlog(string name,Blog *blog): m_name(name), m_blog(blog) {}
~ObserverBlog() {}
void Update() //獲得更新狀態
{
string status = m_blog- >GetStatus();
cout<
客戶的使用方式:
//測試案例
int main()
{
Blog *blog = new BlogCSDN("wuzhekai1985");
Observer *observer1 = new ObserverBlog("tutupig", blog);
blog- >Attach(observer1);
blog- >SetStatus("發表設計模式C++實現(15)——觀察者模式");
blog- >Notify();
delete blog; delete observer1;
return 0;
}
16.狀態模式
狀態模式:允許一個對象在其內部狀態改變時改變它的行為。對象看起來似乎修改了它的類。它有兩種使用情況:(1)一個對象的行為取決于它的狀態, 并且它必須在運行時刻根據狀態改變它的行為。(2)一個操作中含有龐大的多分支的條件語句,且這些分支依賴于該對象的狀態。本文的例子為第一種情況,以戰爭為例,假設一場戰爭需經歷四個階段:前期、中期、后期、結束。當戰爭處于不同的階段,戰爭的行為是不一樣的,也就說戰爭的行為取決于所處的階段,而且隨著時間的推進是動態變化的。下面給出相應的UML圖。
實現的代碼比較簡單,給出War類和State類,War類中含State對象(指針形式)。
class War;
class State
{
public:
virtual void Prophase() {}
virtual void Metaphase() {}
virtual void Anaphase() {}
virtual void End() {}
virtual void CurrentState(War *war) {}
};
//戰爭
class War
{
private:
State *m_state; //目前狀態
int m_days; //戰爭持續時間
public:
War(State *state): m_state(state), m_days(0) {}
~War() { delete m_state; }
int GetDays() { return m_days; }
void SetDays(int days) { m_days = days; }
void SetState(State *state) { delete m_state; m_state = state; }
void GetState() { m_state- >CurrentState(this); }
};
給出具體的狀態類:
//戰爭結束
class EndState: public State
{
public:
void End(War *war) //結束階段的具體行為
{
cout< "戰爭結束"<
使用方式:
//測試案例
int main()
{
War *war = new War(new ProphaseState());
for(int i = 1; i < 40;i += 5)
{
war- >SetDays(i);
war- >GetState();
}
delete war;
return 0;
}
-
適配器
+關注
關注
8文章
1976瀏覽量
68265 -
軟件
+關注
關注
69文章
5013瀏覽量
88089 -
編程語言
+關注
關注
10文章
1950瀏覽量
34989 -
代碼
+關注
關注
30文章
4828瀏覽量
69063 -
設計模式
+關注
關注
0文章
53瀏覽量
8655
發布評論請先 登錄
相關推薦
分類細敘各類電子元器件的失效模式與機理
怎么通過五種運動檢測模式實現應用產品的變革?
GPIO模式與GPIO配置代碼實現
適配器模式實現
ARM Linux:usr模式轉為svc模式的實現原理
基于VxWorks操作系統實現控制應用的復合通信模式設計
![基于VxWorks操作系統<b class='flag-5'>實現</b>控制應用的復合通信<b class='flag-5'>模式</b>設計](https://file.elecfans.com/web1/M00/C9/9D/pIYBAF9_xcqALY0HAAEX4BGtODQ893.png)
十種不同模式實現簡單的計算案例
![十種不同<b class='flag-5'>模式</b><b class='flag-5'>實現</b>簡單的計算案例](https://file.elecfans.com/web1/M00/CB/60/o4YBAF-RAA2AZIIbAAAOaUE5BXE799.png)
分類細敘各類電子元器件的失效模式與機理資料下載
![分類細敘<b class='flag-5'>各類</b>電子元器件的失效<b class='flag-5'>模式</b>與機理資料下載](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
基于ERBAC模式的在線考試模型研究與實現_何利雪
![基于ERBAC<b class='flag-5'>模式</b>的在線考試模型研究與<b class='flag-5'>實現</b>_何利雪](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
評論