代碼大全:類

第二部分 建立高質量的代碼程序員

 

第6章 能夠工做的類編程

在計算時代的早期,程序員基於語句來思考編程問題。到了二十世紀七八十年代,程序員開始基於子程序去思考編程。進入二十一世紀,程序員以類爲基礎思考編程問題。【思考編程的角度有不少,可是寫程序最後都是要落實到語句的層面。語句層面不能精準的實現想要的功能,那麼程序確定會出錯。我沒有深刻過一種如今的面對對象語言,也就是說我對如今的「高效」編程語言瞭解的確很少。所以我不能肯定是否可以不須要花費太多精力在語句實現上,僅僅在類的層面精心設計,在語句上使用輕鬆寫意的手法,就可以很容易得實現功能。不過,若是從編程的進化方向來看,這是個先進的。】數組

類是由一組數據和子程序構成的集合,這些數據和子程序共同擁有一組內聚的、明肯定義的職責。成爲高效程序員的一個關鍵就在於,當你開發程序任一部分的代碼時,都能安全地忽視儘量多的其他部分。而類就是實現這一目標的首要工具。【這聽起來至關不錯,若是你的工程師由多職責明確的小玩意組成的,那麼你一時間能夠只集中注意力在一個小玩意上。】安全

 

6.1 類的基礎:抽象數據類型ADTs數據結構

抽象數據類型是指一些數據以及對這些數據所進行的操做的集合。這些操做既向程序的其他部分描述了這些數據是怎麼樣的,也容許程序的其他部分改變這些數據。抽象數據類型中的數據一詞的用法有些隨意。一個ADT多是一個圖形窗體以及全部能影響該窗體的操做;也能夠是一個文件以及對這個文件進行的操做;或者是一張保險費率表以及相關的操做。【ADT就像一個在現實世界中的實體,你一看過去就知道它是什麼,能對你作什麼】編程語言

傳統的編程教科書在講到抽象數據類型時,總會用一些數學中的事情打岔。這些書每每會這麼寫:你能夠把抽象數據類型想成一個定義有一組操做的數學模型。【哇哦,寫這種書的人必定對數學模型非常瞭解。隱喻經常是很是私密性的,對你感受很好的隱喻對其他人來講卻激不起一樣的感受。不過,使用一些你們都很熟悉的隱喻對象,對於廣泛大衆來講,效果會比較好。】這種書會給人一種感受,好像你從不會真正用到抽象數據類型似的——除非用它來催眠。【我能理解這種感受,對於不少高深的數學名詞個人感受老是深感敬畏,敬而遠之。看來編程大師們也是同樣的,這卻是件新鮮事,我還覺得大家探索數學領域就如同在自家後花園散步同樣呢。】函數

把抽象數據類型解釋得那麼空洞是徹底丟了重點。抽象數據類型可讓你像在現實世界中同樣操縱實體,而沒必要在底層的實現上擺弄實體,這多使人興奮啊。【比起在底層上擺弄變量,語句,來間接控制實體;能直接操縱實體聽起來簡直讓人想留下熱淚。我認可我喜歡在最底層控制機器,這感受棒極了。可是這不表明我認爲在只是要對機器動做作一點微小的改變的狀況下,就必須得翻山越嶺,戰勝各類bug,各類推理,各類流程,作完以後再開個香檳慶祝下。感受上,這得是多麼古老的程序員才能作出的事情啊···仰面長嘆。我但願我有一天可以輕鬆地操縱機器,就像操縱我能輕鬆的操縱我本身的手臂同樣。】你不用再向鏈表中插入一個節點了,而是能夠在電子表格中添加一個數據單元格,或給火車模型加一節車箱。深刻挖掘能在問題領域工做(而非在底層實現領域工做)的能量吧!工具

 

須要用到ADT的例子性能

爲了展開討論,這裏先舉一個例子,看看ADT在什麼狀況下回很是有用。有了例子以後咱們將繼續深刻細節討論。測試

假設你正在寫一個程序,它能用不一樣的字體、字號和文字屬性(如粗體、斜體等)來控制顯示在屏幕上的文本。程序的一部分功能是控制文本的字體。若是你用一個ADT,你就能有捆綁在相關數據上的一組操做字體的子程序——有關的數據包括字體名稱、字號和文字屬性等。這些子程序和數據集合爲一體,就是一個ADT。

若是不使用ADT,你就只能用一種拼湊的方法來操縱字體了。舉例來講,若是你要把字體大小改成12磅,既高度碰巧爲16個像素,你就要寫相似這樣的代碼 

currentFont.size = 16

若是你已經開發了一套子程序庫,那麼代碼可能會稍微好看一點:

currentFont.size = PointsToPixels(12)

或者你還能夠給該屬性起一個更特定的名字,好比說:

currentFont.sizeOnPixels = PointsToPixels(12)

但你不能同時使用currentFont.sizeInPixels 和 currentFont.sizeInPoints,由於若是同時使用 currentFont.sizeInPixels 和 currentFont.sizeInPoints,這兩項數據成員,currentFont 就無從判到底該用哪個。【同時使用兩項數據成員,我聽不出這有什麼問題?】並且,若是你在程序的不少地方都須要修改字體的大小,那麼這類的語句就會散步在整個程序之中。【的確,像這種細枝末節的東西應該被限制使用。最好實現一次原則,完成一個修改只須要在一個地方修改一處就能夠。】

若是你須要把字體設置爲粗體,你或許會寫出下面的語句,這裏用到了一個按位or運算符和一個16進制的常亮0x02:

currentFont.attribute = CurrentFont.attribute or 0x02

若是你夠幸運的話,也可能代碼會比這樣還要乾淨些。但使用拼湊方法的話,你能獲得的最好的結果也就是寫成這樣:

currentFont.attribute = CurrentFont.attribute or BOLD 

或者是這樣:

currentFont.bold = True

就修改字體大小而言,這些作法都存在一個限制,即要求調用方代碼直接控制數據成員,這無疑限制了currentFont的使用。【不須要你直接控制數據成員,只要告訴我你想要作些什麼。這感受比較像人工智能,我一直但願我能夠直接和個人機器交流,問它能作什麼,而不是不停的翻閱NOTE。】

若是你這麼編寫程序的話,程序中的不少地方就會充斥着相似的代碼。【我以前寫的程序也有不少這種代碼,像 currentFont.bold = True 這樣的代碼,可能還被封裝成了函數。簡單的批了一層皮,用處可不大,o(* ̄︶ ̄*)o 改起來基本靠搜索引擎。。。】

 

使用ADT的益處 

問題不在於拼湊法是種很差的編程習慣。而是說你能夠採用一種更好的編程方法來替代這種方法,從而得到下面這些好處。【接下來是說服時間,講講爲何咱們要使用ADTs編程。其實使用ADT的理由能夠很是簡單,它更加「界面友好」,符合人們對美好生活的嚮往,讓一切變得更加簡單。不過,看看,更加理性的理由表是好點子。人們由於感受而行動,可是卻不能單靠感受就將一件須要花費超過幾個小時的時間才能完成的事情作好。】

能夠隱藏實現細節  把關於字體數據類型的信息隱藏起來,意味着若是數據類型發生改變,你只須要在一處修改而不會影響到整個程序。例如,除非你把實現細節隱藏在一個ADT中,不然當你須要把字體類型從粗體的第一種表示變成第二種表示時,就不可避免的要更改程序中全部設置粗體字體的語句,而不能僅在一處進行修改。【在決定外放使用時,就要考慮可能的變動,以及變動意味着什麼。】把信息隱藏起來能保護信息的其他部分不受影響。【對一個模塊的改變最好僅僅在其內發生,模塊的外部接口保持不變。】即便你想把在內存裏存儲的數據改成在外存裏存儲,或者你想把全部操做字體的子程序用另外一種語言重寫,也都不會影響程序的其他部分。

改動不會影響到整個程序  若是想讓字體更豐富,並且能支持更多的操做(例如變成小寫大寫字母、變成上標、添加刪除線等)時,你只須要在程序的一處進行修改便可。這一改動不會影響到程序的其他部分。

讓接口能提供更多的信息  像 currentFont.size = 16 這樣的語句是不夠明確的,由於此處16的單位既多是像素也多是磅。語句所處的上下文環境並不能告訴你究竟是哪種單位。把全部類似的操做都集中到一個ADT裏,就可讓你給予磅數和像素來定義整個接口,或者把兩者明確的區分開,從而有助於避免混淆。

更容易提升性能  若是你想提升操做字體時的性能,就能夠重寫編寫出一些更好的子程序,而不用來回修改整個程序。

讓程序的正確性顯而易見  驗證像 currentFont.attribute = currentFont.attribute or 0x02 這樣的語句是否正確是很枯燥的,你能夠替換成像 currentFont.SetboldOn()這樣的語句,驗證它是否正確就會更容易一些。對於前者,你可能會寫錯結構體或數據項的名字,或者用錯運算符,也可能會寫錯數值。但對於後者,在調用  currentFont.SetboldOn() 時,惟一可能出錯的地方就是寫錯方法名字,所以識別它是否正確就更容易一些。

程序更具自我說明性  你能夠改進像 currentFont.attribute or 0x02 這樣的語句——把0x02換成BOLD或者其餘0x02所表明的具體含義,但不管如何修改,其可讀性都不如 currentFont.SetboldOn() 這條語句。【直接告訴你在作什麼。】

Woodfield、Dunsmore 和 Shen 曾作過這樣一項研究,他們讓一些計算機科學專業的學生回答關於兩個程序的問題:第一個程序按功能分解爲8個子程序,而第二個程序分解爲抽象數據類型中的8個子程序(1981)。結果,那些使用按抽象數據類型程序的學生的得分比使用按功能劃分的程序的學生高出超過30%。【是否是隻有我一我的懷疑這兩個項目的難度不一致?我以爲想得出這種結論,應該將一個程序按兩種方法分解,而後測試。這裏多是記訴錯誤。能夠去查詢下當年的實驗說明。】

無須在程序內處處傳遞數據  在剛纔那個例子裏,你必須直接修改 currentFont 的值,或這把它傳給要操做字節的子程序。若是你使用了抽象數據類型,那麼就不用再在程序裏面處處傳遞 currentFont 了,也無須把它變成全局數據。【抽象數據類型有一個優勢,使用者不須要知道內部實現細節。爲了支持這一點,抽象數據類型須要作好隱私保護工做。因此,本身的變量是不能傳出去給別人修改的,由於其他部分若是須要直接修改你的特徵值,那麼就須要知道你的特徵值的具體使用規則,那麼你的這些具體的條條框框就隨時暴露在其他模塊的視線之下,成爲它們須要額外注意的規則之一。老實說,就沒人可以隨時記得這些,大腦的內存也是頗有限的。】ADT中能夠用一個結構體來保存 currentFont 的數據,而只有ADT裏的子程序才能直接訪問這些數據。ADT以外的子程序則沒必要再關心這些數據。【乍看之下是限制了一部分操做,實際上確實大大方便了各模塊的使用。所謂鬆散耦合。】

你能夠像在現實世界中那樣操做實體,而不用在底層實現上操做它  你能夠定義一些針對字體的操做,這樣,程序的絕大部分就能徹底以「真實世界中的字體」這個概念來操做,而再也不用數組訪問、結構體定義、True 與 False 等這些底層的實現概念了。【天,我一度認爲True好用得不得了。】

這樣以來,爲了定義一個抽象數據類型,你只須要定義一些用來控制字體的子程序——多半就像這樣:

currentFont.SetSizeInPoints(sizeInPoints)

currentFont.SetSizeInPixels(sizeInPixels)

currentFont.SetBoldOn()

currentFont.SetBoldOff()

currentFont.SetItalicOn()

currentFont.SetItalicOff()

currentFont.SetTypeFace(faceName)

這些子程序裏的代碼可能很短——極可能就像你此前看到的那個用拼湊法控制字體時所寫的代碼。【只有一句。實際上全部代碼自己都是拼湊而成的,拼一拼,而後實現個功能。關鍵在於對外的接口足夠「用戶友好」。而內部功能實現則講究另外一套法則,原始規則,要求準確、快速、健壯、佔用內存少。。。】這裏的區別在於,你已經把對字體的操做都隔離到一組子程序中了。這樣就爲須要操做字體的其餘部分程序提供了更好的抽象層,同時它也能夠在針對字體的操做發生變化時提供一層保護。【隔離功能,可使得該功能內部能夠自由的實現。】【這裏要注意,少給我玩假的,一眼就能被看穿。】

 

更多的ADT示例

假設你開發了一套軟件來控制一個核反應堆的冷卻系統。你能夠爲這個冷卻系統規定以下一些操做,從而將其視做一個抽象數據類型:

coolingSystem.GetTemperature()

coolingSystem.SetCirculationRate(rate)  【流通率 / 週期 我也不清楚】

coolingSystem.OpenValue(valueNumber)

coolingSystem.CloseValue(valueNumber)

實現上述各操做的代碼由具體環境決定。程序的其他部分能夠用這些函數來操縱冷卻系統,無須爲數據結構的實現、限制及變化等內部細節而操心。

下面再列舉一些抽象數據類型以及它們可能提供的操做:

巡航控制

設置速度

獲取當前設置

恢復以前的速度

解散 

 

攪拌機 

開啓

關閉

設置速度

啓動「即時粉碎器」

中止「即時粉碎器」

 

油罐 

填充油罐

排空油罐

獲取油罐容積

獲取油罐狀態 

 

列表 

初始化列表

向列表插入條目

從列表刪除條目

讀取列表中的下一個條目

 

燈光

開啓

關閉

 

堆棧

初始化堆棧

像堆棧中推入條目

從堆棧中彈出條目

讀取棧頂條目 

 

幫助屏幕

添加幫助項

刪除幫助項

設置當前幫助項

顯示幫助屏幕

關閉幫助顯示

顯示幫助索引

返回前一屏幕 

 

菜單

開始新的菜單

刪除菜單

添加菜單項

刪除菜單項

激活菜單項

禁用菜單項

顯示菜單

隱藏菜單

獲取菜單選項

 

文件

打開文件

讀取文件

寫入文件

設置當前文件位置

關閉文件

 

指針 

獲取新分配內存的指針

用現有的指針釋放內存

更改已分配的內存大小

 

電梯

到上一層

到下一層

到指定層

報告當前樓層

回到底層

 

【判斷目標有那些操做的訣竅就在於:你想要怎麼使用它?使用測試驅動開發方法會有幫助。】

經過研究這些例子,你能夠得出一些指導建議,下面就來講明這些指導建議:

把常見的底層數據類型建立爲ADT並是有那個這些ADT,而再也不使用底層數據類型  大多數關於ADT的論述中都會關注與把常見的底層數據類型表示爲ADT。從前面的例子中能夠看到,堆棧、列表、隊列以及幾乎全部常見的底層數據類型均可以用ADT來表示。

你可能會問:「這個堆棧、隊列、或列表又是表明什麼呢?」若是堆棧表明的是一組員工,就該把它看做是一些員工而不是堆棧;若是列表表明的是一個出場演員名單,就該把它看做是出場演員名單而不是列表;若是隊列表明的是電子表格中的一組單元格,就該把它看做是一組單元格而不是一個通常的隊列。【對了,事物自己的特殊性!我總不能由於以爲程序像水母,那麼就想辦法將水母的全部特性都加在程序的身上,好比優雅可愛,並直接忽視程序自己的特性,好比,底層是語句/命令拼湊而成。】

相關文章
相關標籤/搜索