第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