C++有三個最重要的特點,即繼承、封裝、多態。我發現其實C語言也是可以面向對象的,也是可以應用設計模式的,關鍵就在於如何實現面向對象語言的三個重要屬性。
(1)繼承性
- typedef struct _parent
- {
- int data_parent;
- }Parent;
- typedef struct _Child
- {
- struct _parent parent;
- 10. int data_child;
- 11.
12. }Child;
在設計C語言繼承性的時候,我們需要做的就是把基礎數據放在繼承的結構的首位置即可。這樣,不管是數據的訪問、數據的強轉、數據的訪問都不會有什么問題。
(2)封裝性
- struct _Data;
- typedef void (*process)(struct _Data* pData);
- typedef struct _Data
- {
- int value;
- process pProcess;
10. }Data;
封裝性的意義在於,函數和數據是綁在一起的,數據和數據是綁在一起的。這樣,我們就可以通過簡單的一個結構指針訪問到所有的數據,遍歷所有的函數。封裝性,這是類擁有的屬性,當然也是數據結構體擁有的屬性。
(3)多態
- typedef struct _Play
- {
- void* pData;
- void (*start_play)(struct _Play* pPlay);
- }Play;
多態,就是說用同一的接口代碼處理不同的數據。比如說,這里的Play結構就是一個通用的數據結構,我們也不清楚pData是什么數據,start_play是什么處理函數?但是,我們處理的時候只要調用pPlay->start_play(pPlay)就可以了。剩下來的事情我們不需要管,因為不同的接口會有不同的函數去處理,我們只要學會調用就可以了。
不知不覺當中,我們就到了最后一種設計模式,即訪問者模式。訪問者模式,聽上去復雜一些。但是,這種模式用簡單的一句話說,就是不同的人對不同的事物有不同的感覺。比如說吧,豆腐可以做成麻辣豆腐,也可以做成臭豆腐。可是,不同的地方的人未必都喜歡這兩種豆腐。四川的朋友可能更喜歡辣豆腐,江浙的人就可能對臭豆腐更喜歡一些。那么,這種情況應該怎么用設計模式表達呢?
- typedef struct _Tofu
- {
- int type;
- void (*eat) (struct _Visitor* pVisitor, struct _Tofu* pTofu);
- }Tofu;
- typedef struct _Visitor
- {
- int region;
- 10. void (*process)(struct _Tofu* pTofu, struct _Visitor* pVisitor);
11. }Visitor;
就是這樣一個豆腐,eat的時候就要做不同的判斷了。
- void eat(struct _Visitor* pVisitor, struct _Tofu* pTofu)
- {
- assert(NULL != pVisitor && NULL != pTofu);
- pVisitor->process(pTofu, pVisitor);
- }
既然eat的操作最后還是靠不同的visitor來處理了,那么下面就該定義process函數了。
- void process(struct _Tofu* pTofu, struct _Visitor* pVisitor)
- {
- assert(NULL != pTofu && NULL != pVisitor);
- if(pTofu->type == SPICY_FOOD && pVisitor->region == WEST ||
- pTofu->type == STRONG_SMELL_FOOD && pVisitor->region == EAST)
- {
- printf("I like this food!\n");
- return;
- 10. }
- 11.
- 12. printf("I hate this food!\n");
13. }
狀態模式是協議交互中使用得比較多的模式。比如說,在不同的協議中,都會存在啟動、保持、中止等基本狀態。那么怎么靈活地轉變這些狀態就是我們需要考慮的事情。假設現在有一個state,
- typedef struct _State
- {
- void (*process)();
- struct _State* (*change_state)();
- }State;
說明一下,這里定義了兩個變量,分別process函數和change_state函數。其中proces函數就是普通的數據操作,
- void normal_process()
- {
- printf("normal process!\n");
- }
change_state函數本質上就是確定下一個狀態是什么。
- struct _State* change_state()
- {
- State* pNextState = NULL;
- pNextState = (struct _State*)malloc(sizeof(struct _State));
- assert(NULL != pNextState);
- pNextState ->process = next_process;
- pNextState ->change_state = next_change_state;
- 10. return pNextState;
11. }
所以,在context中,應該有一個state變量,還應該有一個state變換函數。
- typedef struct _Context
- {
- State* pState;
- void (*change)(struct _Context* pContext);
- }Context;
- void context_change(struct _Context* pContext)
- {
- 10. State* pPre;
- 11. assert(NULL != pContext);
- 12.
- 13. pPre = pContext->pState;
- 14. pContext->pState = pPre->changeState();
- 15. free(pPre);
- 16. return;
17. }
命令模式的目的主要是為了把命令者和執行者分開。老規矩,舉個范例吧。假設李老板是一家公司的頭兒,他現在讓他的秘書王小姐去送一封信。王小姐當然不會自己親自把信送到目的地,她會把信交給郵局來完成整個投遞的全過程。現在,我們就對投遞者、命令、發令者分別作出定義。
首先定義post的相關數據。
- typedef struct _Post
- {
- void (*do)(struct _Post* pPost);
- }Post;
Post完成了實際的投遞工作,那么命令呢?
- typedef struct _Command
- {
- void* pData;
- void (*exe)(struct _Command* pCommand);
- }Command;
- void post_exe(struct _Command* pCommand)
- {
- 10. assert(NULL != pCommand);
- 11.
- 12. (Post*)(pCommand->pData)->do((Post*)(pCommand->pData));
- 13. return;
14. }
我們看到了Post、Command的操作,那么剩下的就是boss的定義了。
- typedef struct _Boss
- {
- Command* pCommand;
- void (*call)(struct _Boss* pBoss);
- }Boss;
- void boss_call(struct _Boss* pBoss)
- {
- assert(NULL != pBoss);
- 10.
- 11. pBoss->pCommand->exe(pBoss->pCommand);
- 12. return;
13. }
解釋器模式雖然聽上去有些費解,但是如果用示例說明一下就不難理解了。我們知道在C語言中,關於變量的定義是這樣的:一個不以數字開始的由字母、數字和下划線構成的字符串。這種形式的表達式可以用狀態自動機解決,當然也可以用解釋器的方式解決。
- typedef struct _Interpret
- {
- int type;
- void* (*process)(void* pData, int* type, int* result);
- }Interpret;
上面的數據結構比較簡單,但是很能說明問題。就拿變量來說吧,這里就可以定義成字母的解釋器、數字解釋器、下划線解釋器三種形式。所以,我們可以進一步定義一下process的相關函數。
- #define DIGITAL_TYPE 1
- #define LETTER_TYPE 2
- #define BOTTOM_LINE 3
- void* digital_process(void* pData, int* type, int* result)
- {
- UINT8* str;
- assert(NULL != pData && NULL != type && NULL != result);
- 10. str = (UNT8*)pData;
- 11. while (*str >= '0' && *str <= '9')
- 12. {
- 13. str ++;
- 14. }
- 15.
- 16. if(*str == '\0')
- 17. {
- 18. *result = TRUE;
- 19. return NULL;
- 20. }
- 21.
- 22. if(*str == '_')
- 23. {
- 24. *result = TRUE;
- 25. *type = BOTTOM_TYPE;
- 26. return str;
- 27. }
- 28.
- 29. if(*str >= 'a' && *str <= 'z' || *str >= 'A' && *str <= 'Z')
- 30. {
- 31. *result = TRUE;
- 32. *type = LETTER_TYPE;
- 33. return str;
- 34. }
- 35.
- 36. *result = FALSE;
- 37. return NULL;
38. }
- 39.
40. void* letter_process(void* pData, int* type, int* result)
41. {
- 42. UINT8* str;
- 43. assert(NULL != pData && NULL != type && NULL != result);
- 44.
- 45. str = (UNT8*)pData;
- 46. while (*str >= 'a' && *str <= 'z' || *str >= 'A' && *str <= 'Z')
- 47. {
- 48. str ++;
- 49. }
- 50.
- 51. if(*str == '\0')
- 52. {
- 53. *result = TRUE;
- 54. return NULL;
- 55. }
- 56.
- 57. if(*str == '_')
- 58. {
- 59. *result = TRUE;
- 60. *type = BOTTOM_TYPE;
- 61. return str;
- 62. }
- 63.
- 64. if(*str >= '0' && *str <= '9')
- 65. {
- 66. *result = TRUE;
- 67. *type = DIGITAL_TYPE;
- 68. return str;
- 69. }
- 70.
- 71. *result = FALSE;
- 72. return NULL;
73. }
- 74.
75. void* bottom_process(void* pData, int* type, int* result)
76. {
- 77. UINT8* str;
- 78. assert(NULL != pData && NULL != type && NULL != result);
- 79.
- 80. str = (UNT8*)pData;
- 81. while ('_' == *str )
- 82. {
- 83. str ++;
- 84. }
- 85.
- 86. if(*str == '\0')
- 87. {
- 88. *result = TRUE;
- 89. return NULL;
- 90. }
- 91.
- 92. if(*str >= 'a' && *str <= 'z' || *str >= 'A' && *str <= 'Z')
- 93. {
- 94. *result = TRUE;
- 95. *type = LETTER_TYPE;
- 96. return str;
- 97. }
- 98.
- 99. if(*str >= '0' && *str <= '9')
- {
- *result = TRUE;
- *type = DIGITAL_TYPE;
- return str;
- }
- *result = FALSE;
- return NULL;
- }
備忘錄模式的起源來自於撤銷的基本操作。有過word軟件操作經驗的朋友,應該基本上都使用過撤銷的功能。舉個例子,假設你不小心刪除了好幾個段落的文字,這時候你應該怎么辦呢?其實要做的很簡單,單擊一些【撤銷】就可以全部搞定了。撤銷按鈕給我們提供了一次反悔的機會。
既然是撤銷,那么我們在進行某種動作的時候,就應該創建一個相應的撤銷操作?這個撤銷操作的相關定義可以是這樣的。
- typedef struct _Action
- {
- int type;
- struct _Action* next;
- void* pData;
- void (*process)(void* pData);
- }Action;
數據結構中定義了兩個部分:撤銷的數據、恢復的操作。那么這個撤銷函數應該有一個創建的函數,還有一個恢復的函數。所以,作為撤銷動作的管理者應該包括,
- typedef struct _Organizer
- {
- int number;
- Action* pActionHead;
- Action* (*create)();
- void (*restore)(struct _Organizer* pOrganizer);
- }Organizer;
既然數據在創建和修改的過程中都會有相應的恢復操作,那么要是真正恢復原來的數據也就變得非常簡單了。
- void restore(struct _Organizer* pOrganizer)
- {
- Action* pHead;
- assert(NULL != pOrganizer);
- pHead = pOrganizer->pActionHead;
- pHead->process(pHead->pData);
- pOrganizer->pActionHead = pHead->next;
- pOrganizer->number --;
- 10. free(pHead);
- 11. return;
12. }
觀察者模式可能是我們在軟件開發中使用得比較多的一種設計模式。為什么這么說?大家可以聽我一一到來。我們知道,在windows的軟件中,所有的界都是由窗口構成的。對話框是窗口,菜單是窗口,工具欄也是窗口。那么這些窗口,在很多情況下要對一些共有的信息進行處理。比如說,窗口的放大,窗口的減小等等。面對這一情況,觀察者模式就是不錯的一個選擇。
首先,我們可以對這些共有的object進行提煉。
- typedef struct _Object
- {
- observer* pObserverList[MAX_BINDING_NUMBER];
- int number;
- void (*notify)(struct _Object* pObject);
- void (*add_observer)(observer* pObserver);
- void (*del_observer)(observer* pObserver);
10. }Object;
其實,我們需要定義的就是觀察者本身了。就像我們前面說的一樣,觀察者可以是菜單、工具欄或者是子窗口等等。
- typedef struct _Observer
- {
- Object* pObject;
- void (*update)(struct _Observer* pObserver);
- }Observer;
緊接着,我們要做的就是在Observer創建的時候,把observer自身綁定到Object上面。
- void bind_observer_to_object(Observer* pObserver, Object* pObject)
- {
- assert(NULL != pObserver && NULL != pObject);
- pObserver->pObject = pObject;
- pObject->add_observer(pObserver);
- }
- void unbind_observer_from_object(Observer* pObserver, Object* pObject)
10. {
- 11. assert(NULL != pObserver && NULL != pObject);
- 12.
- 13. pObject->del_observer(observer* pObserver);
- 14. memset(pObserver, 0, sizeof(Observer));
15. }
既然Observer在創建的時候就把自己綁定在某一個具體的Object上面,那么Object發生改變的時候,統一更新操作就是一件很容易的事情了。
- void notify(struct _Object* pObject)
- {
- Obserer* pObserver;
- int index;
- assert(NULL != pObject);
- for(index = 0; index < pObject->number; index++)
- {
- pObserver = pObjecet->pObserverList[index];
- 10. pObserver->update(pObserver);
- 11. }
12. }
在以往的軟件開發過程中,我們總是強調模塊之間要低耦合,模塊本身要高內聚。那么,可以通過哪些設計模式來實現呢?橋接模式就是不錯的一個選擇。我們知道,在現實的軟件開發過程當中,用戶的要求是多種多樣的。比如說,有這么一個餃子店吧。假設餃子店原來只賣肉餡的餃子,可是后來一些吃素的顧客說能不能做一些素的餃子。聽到這些要求的老板自然不敢怠慢,所以也開始賣素餃子。之后,又有顧客提出,現在的肉餡餃子只有豬肉的,能不能做點牛肉、羊肉餡的餃子?一些只吃素的顧客也有意見了,他們建議能不能增加一些素餡餃子的品種,什么白菜餡的、韭菜餡的,都可以做一點。由此看來,顧客的要求是一層一層遞增的。關鍵是我們如何把顧客的要求和我們的實現的接口進行有效地分離呢?
其實我們可以這么做,通常的產品還是按照共同的屬性進行歸類。
- typedef struct _MeatDumpling
- {
- void (*make)();
- }MeatDumpling;
- typedef struct _NormalDumpling
- {
- void (*make)();
- }NormalDumpling;
上面只是對餃子進行歸類。第一類是對肉餡餃子的歸類,第二類是對素餡餃子的歸類,這些地方都沒有什么特別之處。那么,關鍵是我們怎么把它和顧客的要求聯系在一起呢?
- typedef struct _DumplingReuqest
- {
- int type;
- void* pDumpling;
- }DumplingRequest;
這里定義了一個餃子買賣的接口。它的特別支持就在於兩個地方,第一是我們定義了餃子的類型type,這個type是可以隨便擴充的;第二就是這里的pDumpling是一個void*指針,只有把它和具體的dumpling綁定才會衍生出具體的含義。
- void buy_dumpling(DumplingReuqest* pDumplingRequest)
- {
- assert(NULL != pDumplingRequest);
- if(MEAT_TYPE == pDumplingRequest->type)
- return (MeatDumpling*)(pDumplingRequest->pDumpling)->make();
- else
- return (NormalDumpling*)(pDumplingRequest->pDumpling)->make();
- }
如果說前面的工廠模式是對接口進行抽象化處理,那么建造者模式更像是對流程本身的一種抽象化處理。這話怎么理解呢?大家可以聽我慢慢到來。以前買電腦的時候,大家都喜歡自己組裝機器。一方面可以滿足自己的個性化需求,另外一方面也可以在價格上得到很多實惠。但是電腦是由很多部分組成的,每個廠家都只負責其中的一部分,而且相同的組件也有很多的品牌可以從中選擇。這對於我們消費者來說當然非常有利,那么應該怎么設計呢?
- typedef struct _AssemblePersonalComputer
- {
- void (*assemble_cpu)();
- void (*assemble_memory)();
- void (*assemble_harddisk)();
- }AssemblePersonalComputer;
對於一個希望配置intel cpu,samsung 內存、日立硬盤的朋友。他可以這么設計,
- void assemble_intel_cpu()
- {
- printf("intel cpu!\n");
- }
- void assemble_samsung_memory()
- {
- printf("samsung memory!\n");
- }
- 10.
11. void assemble_hitachi_harddisk()
12. {
- 13. printf("hitachi harddisk!\n");
14. }
而對於一個希望配置AMD cpu, kingston內存、西部數據硬盤的朋友。他又該怎么做呢?
- void assemble_amd_cpu()
- {
- printf("amd cpu!\n");
- }
- void assemble_kingston_memory()
- {
- printf("kingston memory!\n");
- }
- 10.
11. void assmeble_western_digital_harddisk()
12. {
- 13. printf("western digital harddisk!\n");
14. }
中介者模式,聽上去有一點陌生。但是,只要我給朋友們打個比方就明白了。早先自由戀愛沒有現在那么普遍的時候,男女之間的相識還是需要通過媒婆之間才能相互認識。男孩對女方有什么要求,可以通過媒婆向女方提出來;當然,女方有什么要求也可以通過媒婆向男方提出來。所以,中介者模式在我看來,就是媒婆模式。
- typedef struct _Mediator
- {
- People* man;
- People* woman;
- }Mediator;
上面的數據結構是給媒婆的,那么當然還有一個數據結構是給男方、女方的。
- typedef struct _People
- {
- Mediator* pMediator;
- void (*request)(struct _People* pPeople);
- void (*process)(struct _Peoplle* pPeople);
- }People;
所以,這里我們看到的如果是男方的要求,那么這個要求應該女方去處理啊,怎么處理呢?
- void man_request(struct _People* pPeople)
- {
- assert(NULL != pPeople);
- pPeople->pMediator->woman->process(pPeople->pMediator->woman);
- }
上面做的是男方向女方提出的要求,所以女方也可以向男方提要求了。畢竟男女平等嘛。
- void woman_request(struct _People* pPeople)
- {
- assert(NULL != pPeople);
- pPeople->pMediator->man->process(pPeople->pMediator->man);
- }
策略模式就是用統一的方法接口分別對不同類型的數據進行訪問。比如說,現在我們想用pc看一部電影,此時應該怎么做呢?看電影嘛,當然需要各種播放電影的方法。rmvb要rmvb格式的方法,avi要avi的方法,mpeg要mpeg的方法。可是事實上,我們完全可以不去管是什么文件格式。因為播放器對所有的操作進行了抽象,不同的文件會自動調用相應的訪問方法。
- typedef struct _MoviePlay
- {
- struct _CommMoviePlay* pCommMoviePlay;
- }MoviePlay;
- typedef struct _CommMoviePlay
- {
- HANDLE hFile;
- 10. void (*play)(HANDLE hFile);
- 11.
12. }CommMoviePlay;
這個時候呢,對於用戶來說,統一的文件接口就是MoviePlay。接下來的一個工作,就是編寫一個統一的訪問接口。
- void play_movie_file(struct MoviePlay* pMoviePlay)
- {
- CommMoviePlay* pCommMoviePlay;
- assert(NULL != pMoviePlay);
- pCommMoviePlay = pMoviePlay->pCommMoviePlay;
- pCommMoviePlay->play(pCommMoviePlay->hFile);
- }
最后的工作就是對不同的hFile進行play的實際操作,寫簡單一點就是,
- void play_avi_file(HANDLE hFile)
- {
- printf("play avi file!\n");
- }
- void play_rmvb_file(HANDLE hFile)
- {
- printf("play rmvb file!\n");
- }
- 10.
11. void play_mpeg_file(HANDLE hFile)
12. {
- 13. printf("play mpeg file!\n");
14. }
現在的生活當中,我們離不開各種電子工具。什么筆記本電腦、手機、mp4啊,都離不開充電。既然是充電,那么就需要用到充電器。其實從根本上來說,充電器就是一個個普通的適配器。什么叫適配器呢,就是把220v、50hz的交流電壓編程5~12v的直流電壓。充電器就干了這么一件事情。
那么,這樣的一個充電適配器,我們應該怎么用c++描述呢?
- class voltage_12v
- {
- public:
- voltage_12v() {}
- virtual ~voltage_12v() {}
- virtual void request() {}
- };
- class v220_to_v12
10. {
11. public:
- 12. v220_to_v12() {}
- 13. ~v220_to_v12() {}
- 14. void voltage_transform_process() {}
15. };
- 16.
17. class adapter: public voltage_12v
18. {
- 19. v220_to_v12* pAdaptee;
- 20.
21. public:
- 22. adapter() {}
- 23. ~adapter() {}
- 24.
- 25. void request()
- 26. {
- 27. pAdaptee->voltage_transform_process();
- 28. }
29. };
通過上面的代碼,我們其實可以這樣理解。類voltage_12v表示我們的最終目的就是為了獲得一個12v的直流電壓。當然獲得12v可以有很多的方法,利用適配器轉換僅僅是其中的一個方法。adapter表示適配器,它自己不能實現220v到12v的轉換工作,所以需要調用類v220_to_v12的轉換函數。所以,我們利用adapter獲得12v的過程,其實就是調用v220_to_v12函數的過程。
不過,既然我們的主題是用c語言來編寫適配器模式,那么我們就要實現最初的目標。這其實也不難,關鍵一步就是定義一個Adapter的數據結構。然后把所有的Adapter工作都由Adaptee來做,就是這么簡單。不知我說明白了沒有?
- typdef struct _Adaptee
- {
- void (*real_process)(struct _Adaptee* pAdaptee);
- }Adaptee;
- typedef struct _Adapter
- {
- void* pAdaptee;
- void (*transform_process)(struct _Adapter* pAdapter);
- 10.
11. }Adapter;
十三、 C語言和設計模式(裝飾模式)
裝飾模式是比較好玩,也比較有意義。其實就我個人看來,它和責任鏈還是蠻像的。只不過一個是比較判斷,一個是迭代處理。裝飾模式就是那種迭代處理的模式,關鍵在哪呢?我們可以看看數據結構。
- typedef struct _Object
- {
- struct _Object* prev;
- void (*decorate)(struct _Object* pObject);
- }Object;
裝飾模式最經典的地方就是把pObject這個值放在了數據結構里面。當然,裝飾模式的奧妙還不僅僅在這個地方,還有一個地方就是迭代處理。我們可以自己隨便寫一個decorate函數試試看,
- void decorate(struct _Object* pObeject)
- {
- assert(NULL != pObject);
- if(NULL != pObject->prev)
- pObject->prev->decorate(pObject->prev);
- printf("normal decorate!\n");
- }
所以,裝飾模式的最重要的兩個方面就體現在:prev參數和decorate迭代處理。
十四、 C語言和設計模式(享元模式)
享元模式看上去有點玄乎,但是其實也沒有那么復雜。我們還是用示例說話。比如說,大家在使用電腦的使用應該少不了使用WORD軟件。使用WORD呢, 那就少不了設置模板。什么模板呢,比如說標題的模板,正文的模板等等。這些模板呢,又包括很多的內容。哪些方面呢,比如說字體、標號、字距、行距、大小等等。
- typedef struct _Font
- {
- int type;
- int sequence;
- int gap;
- int lineDistance;
- void (*operate)(struct _Font* pFont);
10. }Font;
上面的Font表示了各種Font的模板形式。所以,下面的方法就是定制一個FontFactory的結構。
- typedef struct _FontFactory
- {
- Font** ppFont;
- int number;
- int size;
- Font* GetFont(struct _FontFactory* pFontFactory, int type, int sequence, int gap, int lineDistance);
- }FontFactory;
這里的GetFont即使對當前的Font進行判斷,如果Font存在,那么返回;否則創建一個新的Font模式。
- Font* GetFont(struct _FontFactory* pFontFactory, int type, int sequence, int gap, int lineDistance)
- {
- int index;
- Font* pFont;
- Font* ppFont;
- if(NULL == pFontFactory)
- return NULL;
- 10. for(index = 0; index < pFontFactory->number; index++)
- 11. {
- 12. if(type != pFontFactory->ppFont[index]->type)
- 13. continue;
- 14.
- 15. if(sequence != pFontFactory->ppFont[index]->sequence)
- 16. continue;
- 17.
- 18. if(gap != pFontFactory->ppFont[index]->gap)
- 19. continue;
- 20.
- 21. if(lineDistance != pFontFactory->ppFont[index]->lineDistance)
- 22. continue;
- 23.
- 24. return pFontFactory->ppFont[index];
- 25. }
- 26.
- 27. pFont = (Font*)malloc(sizeof(Font));
- 28. assert(NULL != pFont);
- 29. pFont->type = type;
- 30. pFont->sequence = sequence;
- 31. pFont->gap = gap;
- 32. pFont->lineDistance = lineDistance;
- 33.
- 34. if(pFontFactory-> number < pFontFactory->size)
- 35. {
- 36. pFontFactory->ppFont[index] = pFont;
- 37. pFontFactory->number ++;
- 38. return pFont;
- 39. }
- 40.
- 41. ppFont = (Font**)malloc(sizeof(Font*) * pFontFactory->size * 2);
- 42. assert(NULL != ppFont);
- 43. memmove(ppFont, pFontFacoty->ppFont, pFontFactory->size);
- 44. free(pFontFactory->ppFont);
- 45. pFontFactory->size *= 2;
- 46. pFontFactory->number ++;
- 47. ppFontFactory->ppFont = ppFont;
- 48. return pFont;
49. }
十五、 C語言和設計模式(代理模式)
代理模式是一種比較有意思的設計模式。它的基本思路也不復雜。舉個例子來說,以前在學校上網的時候,並不是每一台pc都有上網的權限的。比如說,現在有pc1、pc2、pc3,但是只有pc1有上網權限,但是pc2、pc3也想上網,此時應該怎么辦呢?
此時,我們需要做的就是在pc1上開啟代理軟件,同時把pc2、pc3的IE代理指向pc1即可。這個時候,如果pc2或者pc3想上網,那么報文會先指向pc1,然后pc1把Internet傳回的報文再發給pc2或者pc3。這樣一個代理的過程就完成了整個的上網過程。
在說明完整的過程之后,我們可以考慮一下軟件應該怎么編寫呢?
- typedef struct _PC_Client
- {
- void (*request)();
- }PC_Client;
- void ftp_request()
- {
- printf("request from ftp!\n");
- }
- 10.
11. void http_request()
12. {
- 13. printf("request from http!\n");
14. }
- 15.
16. void smtp_request()
17. {
- 18. printf("request from smtp!\n");
19. }
這個時候,代理的操作應該怎么寫呢?怎么處理來自各個協議的請求呢?
- typedef struct _Proxy
- {
- PC_Client* pClient;
- }Proxy;
- void process(Proxy* pProxy)
- {
- assert(NULL != pProxy);
- 10. pProxy->pClient->request();
11. }
外觀模式是比較簡單的模式。它的目的也是為了簡單。什么意思呢?舉個例子吧。以前,我們逛街的時候吃要到小吃一條街,購物要到購物一條街,看書、看電影要到文化一條街。那么有沒有這樣的地方,既可以吃喝玩樂,同時相互又靠得比較近呢。其實,這就是悠閑廣場,遍布全國的萬達廣場就是干了這么一件事。
首先,我們原來是怎么做的。
- typedef struct _FoodSteet
- {
- void (*eat)();
- }FoodStreet;
- void eat()
- {
- printf("eat here!\n");
- }
- 10.
11. typedef struct _ShopStreet
12. {
- 13. void (*buy)();
14. }ShopStreet;
- 15.
16. void buy()
17. {
- 18. printf("buy here!\n");
19. }
- 20.
21. typedef struct _BookStreet
22. {
- 23. void (*read)();
24. }BookStreet;
- 25.
26. void read()
27. {
- 28. printf("read here");
29. }
下面,我們就要在一個plaza里面完成所有的項目,怎么辦呢?
- typedef struct _Plaza
- {
- FoodStreet* pFoodStreet;
- ShopStreet* pShopStreet;
- BookStreet* pBookStreet;
- void (*play)(struct _Plaza* pPlaza);
- }Plaza;
10. void play(struct _Plaza* pPlaza)
11. {
- 12. assert(NULL != pPlaza);
- 13.
- 14. pPlaza->pFoodStreet->eat();
- 15. pPlaza->pShopStreet->buy();
- 16. pPlaza->pBookStreet->read();
17. }
十七、 C語言和設計模式(迭代器模式)
使用過C++的朋友大概對迭代器模式都不會太陌生。這主要是因為我們在編寫代碼的時候離不開迭代器,隊列有迭代器,向量也有迭代器。那么,為什么要迭代器呢?這主要是為了提煉一種通用的數據訪問方法。
比如說,現在有一個數據的容器,
- typedef struct _Container
- {
- int* pData;
- int size;
- int length;
- Interator* (*create_new_interator)(struct _Container* pContainer);
- int (*get_first)(struct _Container* pContainer);
- int (*get_last)(struct _Container* pContainer);
- 10.
11. }Container;
我們看到,容器可以創建迭代器。那什么是迭代器呢?
- typedef struct _Interator
- {
- void* pVector;
- int index;
- int(* get_first)(struct _Interator* pInterator);
- int(* get_last)(struct _Interator* pInterator);
- }Interator;
我們看到,容器有get_first,迭代器也有get_first,這中間有什么區別?
- int vector_get_first(struct _Container* pContainer)
- {
- assert(NULL != pContainer);
- return pContainer->pData[0];
- }
- int vector_get_last(struct _Container* pContainer)
- {
- 10. assert(NULL != pContainer);
- 11.
- 12. return pContainer->pData[pContainer->size -1];
13. }
- 14.
15. int vector_interator_get_first(struct _Interator* pInterator)
16. {
- 17. Container* pContainer;
- 18. assert(NULL != pInterator && NULL != pInterator->pVector);
- 19.
- 20. pContainer = (struct _Container*) (pInterator->pVector);
- 21. return pContainer ->get_first(pContainer);
22. }
- 23.
24. int vector_interator_get_last(struct _Interator* pInterator)
25. {
- 26. Container* pContainer;
- 27. assert(NULL != pInterator && NULL != pInterator->pVector);
- 28.
- 29. pContainer = (struct _Container*) (pInterator->pVector);
- 30. return pContainer ->get_last(pContainer);
31. }
看到上面的代碼之后,我們發現迭代器的操作實際上也是對容器的操作而已。
十八、 C語言和設計模式(抽象工廠模式)
前面我們寫過的工廠模式實際上是對產品的抽象。對於不同的用戶需求,我們可以給予不同的產品,而且這些產品的接口都是一致的。而抽象工廠呢?顧名思義,就是說我們的工廠是不一定的。怎么理解呢,舉個例子。
假設有兩個水果店都在賣水果,都賣蘋果和葡萄。其中一個水果店買白蘋果和白葡萄,另外一個水果店賣紅蘋果和紅葡萄。所以說,對於水果店而言,盡管都在賣水果,但是兩個店賣的品種不一樣。
既然水果不一樣,那我們先定義水果。
- typedef struct _Apple
- {
- void (*print_apple)();
- }Apple;
- typedef struct _Grape
- {
- void (*print_grape)();
- }Grape;
上面分別對蘋果和葡萄進行了抽象,當然它們的具體函數也是不一樣的。
- void print_white_apple()
- {
- printf("white apple!\n");
- }
- void print_red_apple()
- {
- printf("red apple!\n");
- }
- 10.
11. void print_white_grape()
12. {
- 13. printf("white grape!\n");
14. }
- 15.
16. void print_red_grape()
17. {
- 18. printf("red grape!\n");
19. }
完成了水果函數的定義。下面就該定義工廠了,和水果一樣,我們也需要對工廠進行抽象處理。
- typedef struct _FruitShop
- {
- Apple* (*sell_apple)();
- Apple* (*sell_grape)();
- }FruitShop;
所以,對於賣白蘋果、白葡萄的水果店就該這樣設計了,紅蘋果、紅葡萄的水果店亦是如此。
- Apple* sell_white_apple()
- {
- Apple* pApple = (Apple*) malloc(sizeof(Apple));
- assert(NULL != pApple);
- pApple->print_apple = print_white_apple;
- return pApple;
- }
10. Grape* sell_white_grape()
11. {
- 12. Grape* pGrape = (Grape*) malloc(sizeof(Grape));
- 13. assert(NULL != pGrape);
- 14.
- 15. pGrape->print_grape = print_white_grape;
- 16. return pGrape;
17. }
這樣,基本的框架就算搭建完成的,以后創建工廠的時候,
- FruitShop* create_fruit_shop(int color)
- {
- FruitShop* pFruitShop = (FruitShop*) malloc(sizeof(FruitShop));
- assert(NULL != pFruitShop);
- if(WHITE == color)
- {
- pFruitShop->sell_apple = sell_white_apple;
- pFruitShop->sell_grape = sell_white_grape;
- 10. }
- 11. else
- 12. {
- 13. pFruitShop->sell_apple = sell_red_apple;
- 14. pFruitShop->sell_grape = sell_red_grape;
- 15. }
- 16.
- 17. return pFruitShop;
18. }
十九、 C語言和設計模式(責任鏈模式)
責任鏈模式是很實用的一種實際方法。舉個例子來說,我們平常在公司里面難免不了報銷流程。但是,我們知道公司里面每一級的領導的報批額度是不一樣的。比如說,科長的額度是1000元,部長是10000元,總經理是10萬元。
那么這個時候,我們應該怎么設計呢?其實可以這么理解。比如說,有人來找領導報銷費用了,那么領導可以自己先看看自己能不能報。如果費用可以順利報下來當然最好,可是萬一報不下來呢?那就只能請示領導的領導了。
- typedef struct _Leader
- {
- struct _Leader* next;
- int account;
- int (*request)(strcut _Leader* pLeader, int num);
- }Leader;
所以這個時候,我們首先需要設置額度和領導。
- void set_account(struct _Leader* pLeader, int account)
- {
- assert(NULL != pLeader);
- pLeader->account = account;
- return;
- }
- void set_next_leader(const struct _Leader* pLeader, struct _Leader* next)
10. {
- 11. assert(NULL != pLeader && NULL != next);
- 12.
- 13. pLeader->next = next;
- 14. return;
15. }
此時,如果有一個員工過來報銷費用,那么應該怎么做呢?假設此時的Leader是經理,報銷額度是10萬元。所以此時,我們可以看看報銷的費用是不是小於10萬元?少於這個數就OK,反之就得上報自己的領導了。
- int request_for_manager(struct _Leader* pLeader, int num)
- {
- assert(NULL != pLeader && 0 != num);
- if(num < 100000)
- return 1;
- else if(pLeader->next)
- return pLeader->next->request(pLeader->next, num);
- else
- 10. return 0;
11. }
二十、 C語言和設計模式(工廠模式)
工廠模式是比較簡單,也是比較好用的一種方式。根本上說,工廠模式的目的就根據不同的要求輸出不同的產品。比如說吧,有一個生產鞋子的工廠,它能生產皮鞋,也能生產膠鞋。如果用代碼設計,應該怎么做呢?
- typedef struct _Shoe
- {
- int type;
- void (*print_shoe)(struct _Shoe*);
- }Shoe;
就像上面說的,現在有膠鞋,那也有皮鞋,我們該怎么做呢?
- void print_leather_shoe(struct _Shoe* pShoe)
- {
- assert(NULL != pShoe);
- printf("This is a leather show!\n");
- }
- void print_rubber_shoe(struct _Shoe* pShoe)
- {
- assert(NULL != pShoe);
- 10. printf("This is a rubber shoe!\n");
11. }
所以,對於一個工廠來說,創建什么樣的鞋子,就看我們輸入的參數是什么?至於結果,那都是一樣的。
- #define LEATHER_TYPE 0x01
- #define RUBBER_TYPE 0x02
- Shoe* manufacture_new_shoe(int type)
- {
- assert(LEATHER_TYPE == type || RUBBER_TYPE == type);
- Shoe* pShoe = (Shoe*)malloc(sizeof(Shoe));
- assert(NULL != pShoe);
- 10.
- 11. memset(pShoe, 0, sizeof(Shoe));
- 12. if(LEATHER_TYPE == type)
- 13. {
- 14. pShoe->type == LEATHER_TYPE;
- 15. pShoe->print_shoe = print_leather_shoe;
- 16. }
- 17. else
- 18. {
- 19. pShoe->type == RUBBER_TYPE;
- 20. pShoe->print_shoe = print_rubber_shoe;
- 21. }
- 22.
- 23. return pShoe;
24. }
二十一、 C語言和設計模式(之模板模式)
模板對於學習C++的同學,其實並不陌生。函數有模板函數,類也有模板類。那么這個模板模式是個什么情況?我們可以思考一下,模板的本質是什么。比如說,現在我們需要編寫一個簡單的比較模板函數。
- template <typename type>
- int compare (type a, type b)
- {
- return a > b ? 1 : 0;
- }
模板函數提示我們,只要比較的邏輯是確定的,那么不管是什么數據類型,都會得到一個相應的結果。固然,這個比較的流程比較簡單,即使沒有采用模板函數也沒有關系。但是,要是需要拆分的步驟很多,那么又該怎么辦呢?如果相通了這個問題,那么也就明白了什么是template模式。
比方說,現在我們需要設計一個流程。這個流程有很多小的步驟完成。然而,其中每一個步驟的方法是多種多樣的,我們可以很多選擇。但是,所有步驟構成的邏輯是唯一的,那么我們該怎么辦呢?其實也簡單。那就是在基類中除了流程函數外,其他的步驟函數全部設置為virtual函數即可。
- class basic
- {
- public:
- void basic() {}
- virtual ~basic() {}
- virtual void step1() {}
- virtual void step2() {}
- void process()
- {
- 10. step1();
- 11. step2();
- 12. }
13. };
basic的類說明了基本的流程process是唯一的,所以我們要做的就是對step1和step2進行改寫。
- class data_A : public basic
- {
- public:
- data_A() {}
- ~data_A() {}
- void step1()
- {
- printf("step 1 in data_A!\n");
- }
- 10.
- 11. void step2()
- 12. {
- 13. printf("step 2 in data_A!\n");
- 14. }
15. };
所以,按照我個人的理解,這里的template主要是一種流程上的統一,細節實現上的分離。明白了這個思想,那么用C語言來描述template模式就不是什么難事了。
- typedef struct _Basic
- {
- void* pData;
- void (*step1) (struct _Basic* pBasic);
- void (*step2) (struct _Basic* pBasic);
- void (*process) (struct _Basic* pBasic);
- }Basic;
因為在C++中process函數是直接繼承的,C語言下面沒有這個機制。所以,對於每一個process來說,process函數都是唯一的,但是我們每一次操作的時候還是要去復制一遍函數指針。而step1和step2是不同的,所以各種方法可以用來靈活修改自己的處理邏輯,沒有問題。
- void process(struct _Basic* pBasic)
- {
- pBasic->step1(pBasic);
- pBasic->step2(pBasic);
- }
二十二、C語言和設計模式(之組合模式)
組合模式聽說去很玄乎,其實也並不復雜。為什么?大家可以先想一下數據結構里面的二叉樹是怎么回事。為什么就是這么一個簡單的二叉樹節點既可能是葉節點,也可能是父節點?
- typedef struct _NODE
- {
- void* pData;
- struct _NODE* left;
- struct _NODE* right;
- }NODE;
那什么時候是葉子節點,其實就是left、right為NULL的時候。那么如果它們不是NULL呢,那么很明顯此時它們已經是父節點了。那么,我們的這個組合模式是怎么一個情況呢?
- typedef struct _Object
- {
- struct _Object** ppObject;
- int number;
- void (*operate)(struct _Object* pObject);
- }Object;
就是這么一個簡單的數據結構,是怎么實現子節點和父節點的差別呢。比如說,現在我們需要對一個父節點的operate進行操作,此時的operate函數應該怎么操作呢?
- void operate_of_parent(struct _Object* pObject)
- {
- int index;
- assert(NULL != pObject);
- assert(NULL != pObject->ppObject && 0 != pObject->number);
- for(index = 0; index < pObject->number; index ++)
- {
- pObject->ppObject[index]->operate(pObject->ppObject[index]);
- 10. }
11. }
當然,有了parent的operate,也有child的operate。至於是什么操作,那就看自己是怎么操作的了。
- void operate_of_child(struct _Object* pObject)
- {
- assert(NULL != pObject);
- printf("child node!\n");
- }
父節點也好,子節點也罷,一切的一切都是最后的應用。其實,用戶的調用也非常簡單,就這么一個簡單的函數。
- void process(struct Object* pObject)
- {
- assert(NULL != pObject);
- pObject->operate(pObject);
- }
二十三、C語言和設計模式(之原型模式)
原型模式本質上說就是對當前數據進行復制。就像變戲法一樣,一個鴿子變成了兩個鴿子,兩個鴿子變成了三個鴿子,就這么一直變下去。在變的過程中,我們不需要考慮具體的數據類型。為什么呢?因為不同的數據有自己的復制類型,而且每個復制函數都是虛函數。
用C++怎么編寫呢,那就是先寫一個基類,再編寫一個子類。就是這么簡單。
- class data
- {
- public:
- data () {}
- virtual ~data() {}
- virtual class data* copy() = 0;
- };
- class data_A : public data
10. {
11. public:
- 12. data_A() {}
- 13. ~data_A() {}
- 14. class data* copy()
- 15. {
- 16. return new data_A();
- 17. }
18. };
- 19.
20. class data_B : public data
21. {
22. public:
- 23. data_B() {}
- 24. ~data_B() {}
- 25. class data* copy()
- 26. {
- 27. return new data_B();
- 28. }
29. };
那怎么使用呢?其實只要一個通用的調用接口就可以了。
- class data* clone(class data* pData)
- {
- return pData->copy();
- }
就這么簡單的一個技巧,對C來說,當然也不是什么難事。
- typedef struct _DATA
- {
- struct _DATA* (*copy) (struct _DATA* pData);
- }DATA;
假設也有這么一個類型data_A,
- DATA data_A = {data_copy_A};
既然上面用到了這個函數,所以我們也要定義啊。
- struct _DATA* data_copy_A(struct _DATA* pData)
- {
- DATA* pResult = (DATA*)malloc(sizeof(DATA));
- assert(NULL != pResult);
- memmove(pResult, pData, sizeof(DATA));
- return pResult;
- };
使用上呢,當然也不含糊。
- struct _DATA* clone(struct _DATA* pData)
- {
- return pData->copy(pData);
- };
二十四、C語言和設計模式(之單件模式)
有過面試經驗的朋友,或者對設計模式有點熟悉的朋友,都會對單件模式不陌生。對很多面試官而言,單件模式更是他們面試的保留項目。其實,我倒認為,單件模式算不上什么設計模式。最多也就是個技巧。
單件模式要是用C++寫,一般這么寫。
- #include <string.h>
- #include <assert.h>
- class object
- {
- public:
- static class object* pObject;
- static object* create_new_object()
- 10. {
- 11. if(NULL != pObject)
- 12. return pObject;
- 13.
- 14. pObject = new object();
- 15. assert(NULL != pObject);
- 16. return pObject;
- 17. }
- 18.
19. private:
- 20. object() {}
- 21. ~object() {}
22. };
- 23.
24. class object* object::pObject = NULL;
單件模式的技巧就在於類的構造函數是一個私有的函數。但是類的構造函數又是必須創建的?怎么辦呢?那就只有動用static函數了。我們看到static里面調用了構造函數,就是這么簡單。
- int main(int argc, char* argv[])
- {
- object* pGlobal = object::create_new_object();
- return 1;
- }
上面說了C++語言的編寫方法,那C語言怎么寫?其實也簡單。大家也可以試一試。
- typedef struct _DATA
- {
- void* pData;
- }DATA;
- void* get_data()
- {
- static DATA* pData = NULL;
- 10. if(NULL != pData)
- 11. return pData;
- 12.
- 13. pData = (DATA*)malloc(sizeof(DATA));
- 14. assert(NULL != pData);
- 15. return (void*)pData;
16. }
二十五、C語言和設計模式(之開篇)
關於軟件設計方面的書很多,比如《重構》,比如《設計模式》。至於軟件開發方式,那就更多了,什么極限編程、精益方法、敏捷方法。隨着時間的推移,很多的方法又會被重新提出來。
其實,就我個人看來,不管什么方法都離不開人。一個人寫不出二叉樹,你怎么讓他寫?敏捷嗎?你寫一行,我寫一行。還是迭代?寫三行,刪掉兩行,再寫三行。項目的成功是偶然的,但是項目的失敗卻有很多原因,管理混亂、需求混亂、設計低劣、代碼質量差、測試不到位等等。就軟件企業而言,沒有比優秀的文化和出色的企業人才更重要的了。
從軟件設計層面來說,一般來說主要包括三個方面:
(1)軟件的設計受眾,是小孩子、老人、女性,還是專業人士等等;
(2)軟件的基本設計原則,以人為本、模塊分離、層次清晰、簡約至上、適用為先、抽象基本業務等等;
(3)軟件編寫模式,比如裝飾模式、責任鏈、單件模式等等。
從某種意義上說,設計思想構成了軟件的主題。軟件原則是我們在開發中的必須遵循的准繩。軟件編寫模式是開發過程中的重要經驗總結。靈活運用設計模式,一方面利於我們編寫高質量的代碼,另一方面也方便我們對代碼進行維護。畢竟對於廣大的軟件開發者來說,軟件的維護時間要比軟件編寫的時間要多得多。編寫過程中,難免要有新的需求,要和別的模塊打交道,要對已有的代碼進行復用,那么這時候設計模式就派上了用場。我們討論的主題其實就是設計模式。
講到設計模式,人們首先想到的語言就是c#或者是java,最不濟也是c++,一般來說沒有人會考慮到c語言。其實,我認為設計模式就是一種基本思想,過度美化或者神化其實沒有必要。其實閱讀過linux kernel的朋友都知道,linux雖然自身支持很多的文件系統,但是linux自身很好地把這些系統的基本操作都抽象出來了,成為了基本的虛擬文件系統。
舉個例子來說,現在讓你寫一個音樂播放器,但是要支持的文件格式很多,什么ogg,wav,mp3啊,統統要支持。這時候,你會怎么編寫呢?如果用C++語言,你可能會這么寫。
- class music_file
- {
- HANDLE hFile;
- public:
- void music_file() {}
- virtual ~music_file() {}
- virtual void read_file() {}
- virtual void play() {}
- 10. virtual void stop() {}
- 11. virtual void back() {}
- 12. virtual void front() {}
- 13. virtual void up() {}
- 14. virtual void down() {}
15. };
其實,你想想看,如果用C語言能夠完成相同的抽象操作,那不是效果一樣的嗎?
- typedef struct _music_file
- {
- HANDLE hFile;
- void (*read_file)(struct _music_file* pMusicFile);
- void (*play)(struct _music_file* pMusicFile);
- void (*stop)(struct _music_file* pMusicFile);
- void (*back)(struct _music_file* pMusicFile);
- void (*front)(struct _music_file* pMusicFile);
- void (*down)(struct _music_file* pMusicFile);
- 10. void (*up)(struct _music_file* pMusicFile);
11. }music_file;
當然,上面的例子比較簡單,但是也能說明一些問題。寫這篇文章的目的一是希望和朋友們共同學習模式的相關內容,另一方面也希望朋友們能夠活學活用,既不要迷信權威,也不要妄自菲薄。只要付出努力,付出汗水,肯定會有收獲的。有些大環境你改變不了,那就從改變自己開始。萬丈高樓平地起,一步一個腳印才能真真實實學到東西。如果盲目崇拜,言必google、微軟、apple,那么除了帶來幾個唾沫星,還能有什么受用呢?無非白費了口舌而已。
希望和大家共勉。