第24条:将类的实现代码分散到便于管理的数个分类之中

  本条要点:(作者总结)

  •  使用分类机制把类的实现代码划分成易于管理的小块
  • 将应该视为 “私有” 的方法归入名叫 Private 的分类中,以隐藏实现细节

  类中经常容易填满各种方法,而这些方法的代码则全部堆在一个巨大的实现文件里。有时这么做是合理的,因为即便通过重构把这个类打散,效果也不会更好。在此情况下,可以通过 Objective-C 的 “分类”机制,把代码按逻辑划入几个分区中,这对开发与调试都有好处。

  比如说,我们把个人信息建模为类。那么这个类就可能包含下面几个方法:

 1 #import <Foundation/Foundation.h>
 2 
 3 @interface EOCPerson : NSObject
 4 
 5 @property (nonatomic, copy, readonly) NSString *firstName;
 6 @property (nonatomic, copy, readonly) NSString *lastName;
 7 @property (nonatomic, strong, readonly) NSArray *friends;
 8 
 9 - (instancetype)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName;
10 
11 /*Friendship methods*/
12 - (void)addFriend:(EOCPerson *)person;
13 - (void)removeFriend:(EOCPerson *)person;
14 - (BOOL)isFriendsWith:(EOCPerson *)person;
15 
16 /*Work methods*/
17 - (void)performDayWork;
18 - (void)takeVacationFromWork;
19 
20 /*Play methods*/
21 - (void)goToTheCinema;
22 - (void)goToSportsGame;
23 
24 @end

  在实现该类时,所有方法的代码可能会写在一个大文件里。如果还向类中继续添加方法的话,那么源代码文件就会越来越大,变得难于管理。所以说,应该把这样的类分成几个不同的部分。例如,可以用 “分类” 机制把刚才的类改成下面这样:

 1 #import <Foundation/Foundation.h>
 2 
 3 @interface EOCPerson : NSObject
 4 
 5 @property (nonatomic, copy, readonly) NSString *firstName;
 6 @property (nonatomic, copy, readonly) NSString *lastName;
 7 @property (nonatomic, strong, readonly) NSArray *friends;
 8 
 9 - (instancetype)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName;
10 
11 @end
12 
13 @interface EOCPerson (Friendship)
14 /*Friendship methods*/
15 - (void)addFriend:(EOCPerson *)person;
16 - (void)removeFriend:(EOCPerson *)person;
17 - (BOOL)isFriendsWith:(EOCPerson *)person;
18 @end
19 
20 @interface EOCPerson (Work)
21 /*Work methods*/
22 - (void)performDayWork;
23 - (void)takeVacationFromWork;
24 @end
25 
26 @interface EOCPerson (Play)
27 /*Play methods*/
28 - (void)goToTheCinema;
29 - (void)goToSportsGame;
30 @end

  现在,类的实现代码按照方法分成了好几个部分。所以说,这项语言特性当然就叫做 “分类”啦。在本例中,类的基本要素(诸如属性与初始化方法等)都声明在 “主实现”(main implementation)里。执行不同类型的操作所用的另外几套方法则归入各个分类中。

  使用分类机制之后,依然可以把整个类都定义在一个接口文件中,并将代码写在一个实现文件里。可是,随着分类数量增加,当前这份实现文件很快就膨胀得无法管理了。此时可以把每个分类提取到各自的文件中去。以 EOCPerson 为例,可以按照其分类拆分成下列几个文件:

  • EOCPerson+Friendship(.h/.m)
  • EOCPerson+Work(.h/.m)
  • EOCPerson+Play(.h/.m)

比方说,与交友功能相关的那个分类可以这样写:

1 #import "EOCPerson.h"
2 
3 @interface EOCPerson (Friendship)
4 
5 - (void)addFriend:(EOCPerson *)person;
6 - (void)removeFriend:(EOCPerson *)person;
7 - (BOOL)isFriendsWith:(EOCPerson *)person;
8 
9 @end
 1 #import "EOCPerson+Friendship.h"
 2 
 3 @implementation EOCPerson (Friendship)
 4 
 5 - (void)addFriend:(EOCPerson *)person {
 6     /*...*/
 7 }
 8 
 9 - (void)removeFriend:(EOCPerson *)person {
10     /*...*/
11 }
12 
13 - (BOOL)isFriendsWith:(EOCPerson *)person {
14     /*...*/
15     return NO;
16 }
17 
18 @end

  通过分类机制,可以把类代码分成很多个易于管理的小块,以便单独检视。使用分类机制之后,如果想用分类中的方法,那么要记得在引入 EOCPerson.h 时一并引入分类的头文件。虽然稍微有点麻烦,不过分类仍然是一种管理代码的好办法。

  即使类本身不是太大,我们也可以使用分类机制将其切割成几块,把相应代码归入不同的 “功能区”(functional area)中。Cocoa 中的 NSURLRequest 类及其可变版本 NSMutableURLRequest 类是这么做的。这个类用于执行从 URL 中获取数据的请求,而且通常使用 HTTP 协议从因特网中的某个服务器上获取,不过,由于该类设计得较为通用,所以也可以使用其他协议。与标准的URL 请求相比,执行 HTTP 请求时还需要另外一些信息,例如 “HTTP 方法”(这里的 “方法” 是 “动作”的意思,其含义与编程语言中用以称呼函数的 “方法” 一词不同)(HTTP method,GET、POST等)或 HTTP  头(HTTP header)。

  然而却不便从 NSURLRequest 中继承子类以实现 HTTP 协议的特殊需求,因为本类包裹了一套操作 CFURLRequest 数据结构所需的 C 函数,所有 “HTTP 方法” 都包含在这个结构里。于是,为了扩展 NSURLRequest 类,把与 HTTP  有关的方法归入名为 NSHTTPURLRequest 的分类中,而把与可变版本有关的方法归入名为 NSMutableHTTPURLRequest 的分类中。这样,所有底层 CFURLRequest 函数就都封装在同一个 Objective-C  类里了,而在这个类里,与 HTTP 有关的方法却又要单独放在一起,因为若是不这么做的话,该类的使用者就会有疑问:为什么能在使用 FTP  协议的 request 对象上设置 “HTTP 方法” 呢?

  之所以要将代码打散到分类中还有个原因,就是便于调试:对于某个分类中的所有方法来说,分类名称都会出现在其符号中。例如,“addFriend:” 方法的 “符号名”(symbol name)如下:

1 - [EOCPerson(Friendship) addFriend:]

  在调试器的回溯信息中,会看到类似下面这样的内容:

1 frame #2: 0x00001c50 Test '-[EOCPerson(Friendship) addFriend:]
2 + 32 at main.m: 46

   根据回溯信息中的分类名称,很容易就能精确定位到类中的方法所属的功能区,这对于某些应该视为私有的方法来说更是极为有用。可以创建名为 Private 的分类,把这种方法全都放在里面。这个分类里的方法一般只会在类或框架内部使用,而无须对外公布。这样一来,类的使用者有时可能会在回溯信息时发现 private 一词,从而知道不应该直接调用此方法了。这可算作一种编写 “自我描述式代码”(self-documenting code)的办法。

  在编写准备分享给其他开发者使用的程序库时,可以考虑创建 Private 分类。经常会遇到这样一些方法:它们不是公共 API  的一部分,然而却非常适合在程序库之内使用。此时应该创建 Private 分类,如果程序库中的某个地方要用到这些方法,那就引入此分类的头文件。而分类的头文件并不随程序库一并公开,于是该库的使用者也就不知道库里还有这些私方法了。

END

posted @ 2017-07-31 07:37  鳄鱼不怕牙医不怕  阅读(259)  评论(0编辑  收藏  举报