首页 > 代码库 > 第20条:为私有方法名加前缀

第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条:为私有方法名加前缀