第20條:為私有方法名加前綴
本條要點:(作者總結)
- 給私有方法的名稱加上前綴,這樣可以很容易地將其同公共方法區分開。
- 不要單用一個下劃線做私有方法的前綴,因為這樣做法是預留給蘋果公司用的。
一個類所做的事情通常都要比從外面看到的更多。編寫類的實現代碼時,經常要寫一些只在內部使用的方法。筆者建議,應該為這種方法的名稱加上某些前綴,這有助於調試,因為據此很容易就能把公共方法和私有方法區別開。
為私有方法名加前綴還有個原因,就是便於修改方法名或方法簽名。對於公共方法來說,修改其名稱或簽名之前要三思,因為類的公共 API 不便隨意改動。如果改了,那麽使用這個類的所有開發者都必須更新其代碼才行。而對於內部方法來說,若要修改其名稱或簽名,則只需要同時修改本類內部的相關代碼即可,不會影響到面向外界的那些 API。用前綴把私有方法標出來,這樣很容易就能看出哪些方法可以隨意修改,哪些不應輕易改動。
具體使用何種前綴可根據個人喜好來定,其中最好包含下劃線與字母p。筆者喜歡用 p_ 作為前綴,p 表示 “private”(私有的),而下劃線則可以把這個字母和真正的方法名區隔開。下劃線後面的部分按照常用的駝峰法來命名即可,其首字母要小寫。例如,包含私有方法的 EOCObject 類可以這樣寫:
1 #import <Foundation/Foundation.h> 2 3 @interface EOCObject : NSObject 4 5 - (void)publicMethod; 6 7 @end 8 9 #import "EOCObject.h" 10 11 @implementation EOCObject 12 13 - (void)publicMethod { 14 /* ... */ 15 } 16 17 - (void)p_privateMethod { 18 /* ... */ 19 } 20 21 @end
與公共方法不同,私有方法不出現在接口定義中。有時可能要在 “class-continuation 分類”裏聲明私有方法,然而最近修訂的編譯器已經不要求在使用方法前必須先行聲明了。所以說,私有方法一般只在實現的時候聲明。
如果寫過 C++ 或 Java 代碼,你可能就會問了:為什麽要這樣做呢?直接把方法聲明成私有的不就好了嗎?Objective-C 語言沒有辦法將方法標為私有。每個對象都可以響應任意消息,而且可在運行期檢視某個對象所能直接響應的消息。根據給定的消息查出其對應的方法,這一工作要在運行期才能完成,所以 Objective-C 中沒有那種約束方法調用的機制用以限定誰能調用此方法、能在哪個對象上調用此方法以及何時能調用此方法。開發者會在命名慣例中體現出 “私有方法”等語義。新手也許不適應這一點,但是必須用心領悟 Objective-C 語言這種強大的動態特性。想掌握其動態特性,確實得花大功夫,不過培養良好的命名習慣也是一條成功之道。
蘋果公司喜歡單用一個下劃線作為私有方法的前綴。你或許也想照著蘋果公司的辦法只拿一個下劃線作前綴,這樣做可能會惹來大麻煩:如果從蘋果公司提供的某個類中繼承了一個子類,那麽你在子類裏可能會無意間覆寫了父類的同名方法。鑒於此,蘋果公司在文檔中說,開發者不應該單用一個下劃線做前綴。不能將方法限定於某個範圍內,這也許是 Objective-C 的缺點,然而作為 “動態方法派發系統”(dynamic method dispatch system)這個強大組件的一部分,此特性也帶來了諸多好處。
你或許覺得剛才提到的那種情況不太常見,其實未必。例如,要在 iOS 應用程序中創建一個視圖控制器,就得編寫 UIViewController 的子類。自定義的視圖控制器裏可能保存著許多狀態消息。你可能想編寫一個方法,當視圖出現在屏幕上時,可經由此方法把控制器裏的所有狀態都重置一遍。於是,該方法的實現代碼也許會寫成這樣:
1 #import <UIKit/UIKit.h> 2 3 @interface EOCViewController : UIViewController 4 5 @end 6 7 8 #import "EOCViewController.h" 9 10 @interface EOCViewController () 11 12 @end 13 14 @implementation EOCViewController 15 16 - (void)_resetViewController { 17 // Reset state and views 18 19 } 20 21 - (void)viewDidLoad { 22 [super viewDidLoad]; 23 // Do any additional setup after loading the view. 24 } 25 26 - (void)didReceiveMemoryWarning { 27 [super didReceiveMemoryWarning]; 28 // Dispose of any resources that can be recreated. 29 } 30 31 /* 32 #pragma mark - Navigation 33 34 // In a storyboard-based application, you will often want to do a little preparation before navigation 35 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 36 // Get the new view controller using [segue destinationViewController]. 37 // Pass the selected object to the new view controller. 38 } 39 */ 40 41 @end
可問題是,UIViewController 類本身其實已經實現了一個名叫 _resetViewController 的方法了!如果這樣寫的話,那麽所有調用都將執行子類中的這個方法,本來該調用超類方法的地方現在調用的卻是 EOCViewController 中覆寫過的這個版本。由於超類中的同名方法並未對外公布,所以除非深入研究這個庫,否則你根本不會察覺到自己在無意間覆寫了這個方法。這畢竟是個用下劃線開頭的私有方法,所以沒有對外公布也是合理的。由於超類方法永遠不可能執行,所以這個視圖控制器的行為會很奇怪,到時你可能會納悶:為什麽子類的這個方法調用得這個頻繁呢,按道理不應該執行這麽多次呀?
總之,在確定使用了前綴的情況下,如果子類所繼承的那個類既不在蘋果公司的框架中,也不在你自己的項目中,而是來自別的框架,那麽除非該框架在文檔中明示,否則你無法知道其私有方法所加的前綴是什麽。此時可以把自己一貫使用的類名前綴用作子類私有方法的前綴,這樣能有效避免重名問題。同時還應該考慮到其他人會如何從你所寫的類中繼承子類,這也是私有方法應該加前綴的原因。除非使用一些相當復雜的工具,否則,在沒有源代碼的情況下,無法知道某個類在其公共接口之外還 定義並實現了哪些方法。
END
第20條:為私有方法名加前綴