天天看點

《編寫高品質代碼:改善Objective-C程式的61個建議》——建議2:在頭檔案中盡量減少其他頭檔案的引用

本節書摘來自華章出版社《編寫高品質代碼:改善objective-c程式的61個建議》一 書中的第1章,第1.2節,作者:劉一道,更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視。

在面向對象開發語言中,如c++、c#、java等語言中,對于類的描述,通常劃分為頭檔案和源檔案。頭檔案用于描述類的聲明和可公開部分,而源檔案用于描述類的方法或函數的具體實作,這也展現了面向對象語言的“封閉性”和“高内聚低耦合”的特性。而對于基于面向對象而設計的objective-c也不例外,類分為頭檔案(.h)和源檔案(.m)。

在oop程式設計中有兩個技術用于描述類與類或對象與對象之間的關系:一個是繼承;另一個是複合。在objective-c中,當一個類需要引用另一個類,即建立複合關系時,需要在類的頭檔案(.h)中,通過“#import ”修飾符來建立被引用類的指針。例如car.h:

在這裡先省略類car的源檔案(.m)。對于上面的代碼,如果直接這麼編譯,編譯器會報錯,提示它不知道tire和engine是什麼。為了使上面的代碼能編譯通過,在上面代碼中就不得不添加對類tire和engine的頭檔案(.h)的引用,即通過關鍵字“#import”來建立起它們之間的複合關系。

要建立正确的複合關系,正确的代碼寫法如下:

現在,上面的代碼雖然能正确編譯了,但從“代碼的高品質高安全”角度來看,在使用“#import”建立類之間的複合關系時,也暴露了所引用類tire和engine的實體變量和方法,與隻需知道有一個類名叫tire和engine的初衷有些違背。在解決問題的同時,也帶來了代碼的安全性問題。

那麼如何解決上面的問題呢?可以使用關鍵字@class來告訴編譯器:這是一個類,是以隻需要通過指針來引用它。它并不需要知道關于這個類的更多資訊,隻要了解它是通過指針引用即可,減少由依賴關系引起的重新編譯所産生的影響。

對于上面的代碼,通過@class即可來建立對于類tire和engine的引用,具體寫法如下:

上面介紹了使用“#import”和“@class”在“依賴關系”方面所産生的影響。同時二者在編譯效率方面也存在巨大的差異。假如,有100個頭檔案,都用“#import”引用了同一個頭檔案,或者這些檔案是依次引用的,如a–>b、b–>c、c–>d這樣的引用關系。當最開始的那個頭檔案有變化時,後面所有引用它的類都需要重新編譯,如果自己的類有很多的話,這将耗費大量的時間,而使用@class則不會。

對于初學者,最容易犯 “類循環依賴”錯誤。所謂的“類循環依賴”,也就是說,兩個類互相引用對方。在本條款最初的car.h頭檔案中,通過“#import ”引用了tire.h頭檔案,假如在tire.h頭檔案裡引用car.h頭檔案,即如下:

上面的代碼進行編譯時會出現編譯錯誤,如果使用@class在兩個類的頭檔案中互相聲明,則不會有編譯錯誤出現。雖然使用@class不會出現編譯錯誤,但還是盡量避免這種“類循環依賴”的出現,因為這樣容易造成類之間“高耦合”現象的産生,給以後代碼的維護和管理帶來很大的麻煩。

“#import”并非一無是處。既然 “#import”與“@class”相比有很多不足,那麼是否可以用“@class”來完全代替“#import”?不可以,在一個頭檔案(.h)中包含多個類的聲明定義時,要與該頭檔案聲明的多個類建立複合關系,比較好的方式是,采用關鍵字“#import”來建立複合關系。

例如,下面是頭檔案persontype.h的定義:

要與上面頭檔案persontype.h中所聲明的類建立複合關系,這個時候就不得不用關鍵字“#import”。使用“#import”建立複合關系,會把所引用的頭檔案(.h)的所有類進行預編譯,這樣就會消耗很長時間。是否是有一種更好的方式來處理這個問題,請參閱“條款 9 盡量使用子產品方式與多類建立複合關系”。

一般來說,關鍵字“@class”放在頭檔案中隻是為了在頭檔案中引用這個類,把這個類作為一個類型來用。這就要求引用的頭檔案(.h)名與類的名稱一緻,且在類頭檔案(.h)隻包含該類的聲明定義的情況下,才可以使用關鍵字“@class”來建立複合關系。同時,在實作這個類的接口的類源檔案(.m)中,如果需要引用這個類的實體變量或方法等,還需要通過“#import”把在“@class”中聲明的類引用進來。例如下面的類a引用類rectangle的示例:

上面的種種介紹,其核心的目的就是為了“降低類與類之間的耦合度”。也就是說,降低類與類之間的複合關系黏性度。

在自己設計類的時候,除了“#import”和“@class”之外,有沒有一種更好的方式?有的,一種是通過使用子產品方式與多類建立複合關系,詳細情況請參閱建議6;另一種是通過使用“協定”的方式來實作。

在objective-c中,實際上,協定就是一個穿了“馬甲”的接口。通過使用“協定”來降低類與類之間的耦合度。例如,把協定單獨寫在一個檔案中,注意,千萬不要把協定寫入到一個大的頭檔案中,這樣做,凡是隻要引入此協定,就必定會引入頭檔案中的全部内容,如此一來,類與類之間的耦合度就會大大增加,不利于代碼的管理及程式的穩定性和安全性,為以後的工作帶來很大的麻煩。

故此,在自己設計類的時候,首先要明白,通過使用“#import”和“@class”,每次引入其他的頭檔案是否有必要。如果要引用的類和該類所在的檔案同名,最好采用“@class”方式來引入。如果引用的類所處的檔案有多個類或者多個其他的定義,最好采用“子產品方式”來針對性引入自己所需要的類,詳細情況請參與建議6。不管采取哪種方式,降低類與類的耦合度,降低不同檔案代碼之間過度的黏合性是首要的目的。代碼的依賴關系過于複雜則會失去代碼的重用性,給維護代碼帶來很大的麻煩,同時,使編譯的應用的穩定性和高效性也大打折扣。

《編寫高品質代碼:改善Objective-C程式的61個建議》——建議2:在頭檔案中盡量減少其他頭檔案的引用