簡介
在物件導向的系統中,設計模式是可被復用的資料結構成為了gamma的《設計模式》書的中心主題。在這篇文章裡,我將
解釋設計模式的原理,同時我將比較在gamma的書中描繪的迭代器設計模式和標準模板庫中實現的迭代器。
設計模式的歷史摘要
在物件導向程式的早期,物件模型幾乎被認為開創了**設計和復用的新時代,隨著科學技術的發展,現實比宣傳的更復
雜,物件導向的主體是乙個強大的工具,但是並不能導致好的軟體設計,就像磚和灰並不能導致好的建築設計一樣。
在物件導向的系統設計中你僅僅理解物件模型是不夠的,物件是怎樣被建立的?它們是怎樣被初始化的?誰擁有物件例項
?如何訪問物件例項?誰負責銷毀物件?
可以由設計模式來回答這些問題,設計模式描繪了物件復用的關係,類似的,設計模式是物件導向的程式,演算法是結構化
的程式,結構化程式(順序指令,條件,分支)的基本元素中不包含象鍊錶,快速查詢這樣的結構的概念,所以他不是很有效的。
但是明確的說什麼是設計模式?
考慮乙個點陣圖類 bitmap,假設你向要這個 bitmap 類支援多種圖形檔案格式(gif, jpg, bmp, etc).一種方法是你為每一
個檔案型別增加乙個成員函式:
class bitmap這種方法有許多缺點,第一,隨著對大量檔案格式的支援,介面變的日益混亂,支援新的檔案格式需要修改 bitmap 類本;
身,所以這是比較難而且不受歡迎的,假如你想要 bitmap 支援一種應用程式檔案格式,修改 bitmap支援這種格式將汙染介面。
一種比較好的解決這種問題的方式是從類中分離檔案的部分,這要通過定義第二個類:
class bitmapbuilder為了支援新的檔案格式,你需要做的只是繼承bitampbuilder類,並實現readfromfile()函式,jpgbuilder讀jpg檔案,;
bmpbuilder讀bmp檔案,等等。現在你可以實現新的檔案格式以及私有的檔案格式而不用去修改 bitmap介面。
這種觀念,也就是從乙個初始化介面分離類的介面的概念,是 builder 設計模式。
固然,這種模式在gamma和其他人附上"builder"名字之前已經存在。但是,給乙個複雜的概念加上如此簡單的名字卻幫助我們
為物件設計發展了乙個強大的詞彙。
此外,研究 builder的結構幫助我們認識到支援多種檔案格式的問題可以通用到這樣一類的問題,通過乙個模板建立乙個新的
詞處理文件,或者為過關遊戲產生乙個隨機的迷宮設計。
為物件結構加上"builder"這個結構類似於給o(nlog(n))divide-and-conquer查詢演算法增加乙個"quicksort"的名字。每次你
用到它的時候都不用再去解釋它。
設計模式和c++
在它們的書裡gamma和其他人,把他們限制到了傳統的物件導向的語言中,命名繼承和虛函式,這些都是強大的工具,他們並
不是c++程式設計師所用到的工具,
為了舉例說明傳統的物件導向語言和c++語言能達到殊途同歸的原理,我將要比較gamma的書中迭代器的設計模式的描述和標
準模板庫提供的迭代器。
迭代器設計模式
研究乙個能實現多種容器的(列表,向量,二進位制數,等等),容器從乙個抽象的容器類繼承。
templateclass container
;
列表,向量,佇列,樹,等等都是從容器類繼承,並且實現了additem()和removeitem()方法。
如果你想讓客戶端訪問這些容器類的元素,但是你並不知道使用的是哪種容器,迭代器設計模式就象書中描述的一樣,用繼承
和虛函式提供一種解決這種問題的途徑。方法是用第二種物件型別,去提供對容器中元素的訪問。
抽象的迭代器類通過定義客戶端介面來訪問容器的元素。
templatecurrentitem()返回迭代器指向的當前的項,gotofirst()設定迭代器指向容器的起始位置,gotonext()讓迭代器指向容器class iterator
;
內的下一項,如果迭代器指向了容器內元素的最後一項,isdone()返回真。
每個容器的具體的迭代器都是從iterator繼承。listiterator,vectoriterator,treeiterator,dequeiterator,等等,每乙個都為
他們各自的容器實現了迭代器介面。
既然客戶端不用知道他們使用的具體容器,那麼他們也不必去知道建立什麼種類的迭代器,為了解決這個問題,我們給容
器增加乙個createiterator()函式。
template具體的容器實現createiterator()並且返回正確的迭代器型別。class container
;
templateclass list : public container
...};
那麼,我們怎麼用這些迭代器呢?乙個用來列印容器內的每乙個元素的函式看起來就像下面這樣.
templatevoid printelements(container*container)
delete it;
}
這個方法是很有效的,它使你只需要寫一次printelements()函式就能在任何的容器內使用,它也有一些缺點,首先,將來
每乙個新增加的客戶端必須從container 繼承 printelements(),第二,printelements()使三個虛函式在它內部迴圈呼叫。像列印
這種程式可能沒太大的影響,但是大量的迴圈可能就會有問題。
保護這個實現的普遍特徵但同時減輕我們程式的結構和執行的負擔會更令人滿意的。
stl iterators
stl iterators 為它們的容器提供通用的順序訪問,但同時不用虛函式和繼承,怎麼樣?c++實現了stl這個原理,事實上
,在c裡面,迭代器早已經被編譯到了語言裡,它們叫做指標。
想一想下面的c++函式:
template這個函式和前部分介紹的printelements()函式有相同的功效,"iterator"在陣列的開始位置被增加直到延伸到陣列的結void printarray(item *array, size_t len)
}
尾。只不過這個printelements()沒有使用虛函式也沒有要求使用者從任何地方繼承。
僅有的問題是它只為內建的陣列工作,這種方法不能為鍊錶或二近制樹工作,不是麼?
通常,物件導向的設計將機器底層的概念(就像指標和陣列)提公升到更高層次,就像容器和迭代器,stl iterator 卻用了
相反的方法,stl iterators用重栽操作來實現高階別下就像內建指標一樣的行為。++和--操作被用來按順序移動到下乙個或
前乙個元素位置,用 *操作來實現資料訪問。
stl的printelements()版本看起來就像下面這樣:
template這個方法除了支援++,!=和*運算元外跟iterator是一樣的。void printelements(iterator first, iterator last)
}
注意我們如何改變printelements()介面,非常簡單,在乙個容器內替換途徑,我們替換第乙個和最後乙個iterator.這個
改變的意思是printelements()不僅僅支援stl容器,同樣支援內建的c陣列。事實上,printelements()將支援所有支援++,!=和*的
物件。一句話關於模板
對於在這兒介紹的基於繼承的和基於stl版本的printelements()都用到模板,不同的是基於儲存在容器內的型別和之後介
紹基於iterator的形式。
這個不同的結果是乙個系統中用乙個整型的向量,列表和佇列只生成單一版本的printelements()而在基於繼承的版本中
(container),將要產生三個版本,在stl的case 裡(三種迭代器型別,每一種乙個).
這是權衡類的尺寸和速度,**膨脹不是最關心的,現在的系統已經有了幾百兆甚至更多的記憶體。
真正要關心的是模板,事實上,模板必須被宣告為內聯的,那寫做過c或c++大的專案的人或許知道,在標頭檔案中微小的改
變都能很影響編譯時間。
設計模式 迭代器模式 C 實現
迭代器模式 提供一種方法可以順序訪問乙個聚合物件中各個元素,而又不需要暴露給該物件的內部表示。場景 1.乙個聚合物件,如陣列 list,應該提供一種方法來讓別人可以訪問他的元素,而又不需要暴露他的內部結構 2.支援對聚合物件的多種遍歷 3.為遍歷不同的聚合物件結構提供乙個統一的介面。迭代器模式主要實...
用兩個棧實現佇列 C 實現
用兩個棧來實現乙個佇列,完成佇列的push和pop操作。佇列中的元素為int型別。class solution int pop private stackstack1 stackstack2 在執行push時,令stack2為空,將待存元素壓入stack1的棧頂 在執行pop時,令stack1為空,...
php 常用的兩個設計模式
b 1.工廠模式 b 工廠模式提供了乙個在實時狀態下例項化物件的機制。class creditcardprocessor return new provider cc creditcardprocessor factory paypal 執行結果如下圖所示 img b 2.單例模式 b 單例模式在確...