第四章 协议与分类—第24条:将类的实现代码分散到便于管理的数个分类之中

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

#import <Foundation/Foundation.h>

@interface EOCPerson : NSObject
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSArray *friends;

- (id)initWithFirstName:(NSString*)firstName andLastName:(NSString*)lastName;

/* Friendship methods */
- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;
- (BOOL)isFriendsWith:(EOCPerson*)person;

/* Work methods */
- (void)performDaysWork;
- (void)takeVacationFromWork;

/* Play methods */
- (void)goToTheCinema;
- (void)goToSportsGame;

@end

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

#import <Foundation/Foundation.h>

@interface EOCPerson : NSObject
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSArray *friends;

- (id)initWithFirstName:(NSString*)firstName 
            andLastName:(NSString*)lastName;
@end

@interface EOCPerson (Friendship)
- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;
- (BOOL)isFriendsWith:(EOCPerson*)person;
@end

@interface EOCPerson (Work)
- (void)performDaysWork;
- (void)takeVacationFromWork;
@end

@interface EOCPerson (Play)
- (void)goToTheCinema;
- (void)goToSportsGame;
@end

现在,类的实现代码按照方法分成了好几个部分。所以说,这项语言特性当然就叫做"分类"啦。在本例中,类的基本要素(诸如属性与初始化方法等)都声明在"主实现"(main implementation)里。执行不同类型的操作所用的另外几套方法则归入各个分类中。
使用分类机制之后,依然可以把整个类都定义在一个接口文件中,并将其代码写在一个实现文件里。可是,随着分类数量增加,当前这份实现文件很快就膨胀得无法管理了。此时可以把每个分类提取到各自的文件中去。以EOCPerson为例,可以按照其分类拆分成下列几个文件:

  • EOCPerson + Friendship(.h/.m)
  • EOCPerson + Work(.h/.m)
  • EOCPerson + Play(.h/.m)
    比方说,与交友功能相关的那个分类可以这样写:
// EOCPerson+Friendship.h
#import "EOCPerson.h"

@interface EOCPerson (Friendship)
- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;
- (BOOL)isFriendsWith:(EOCPerson*)person;
@end

// EOCPerson+Friendship.m
#import "EOCPerson+Friendship.h"

@implementation EOCPerson (Friendship)
- (void)addFriend:(EOCPerson*)person {
    …
}
- (void)removeFriend:(EOCPerson*)person {
    …
}
- (BOOL)isFriendsWith:(EOCPerson*)person {
    …
}
@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)如下:

- [EOCPerson(Friendship) addFriend:]

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

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

根据回溯信息中的分类名称,很容易就能精确定位到类中的方法所属的功能区,这对于某些应该视为私有的方法来说更是极为有用。可以创建名为Private的分类,把这种方法全都放在里面。这个分类里的方法一般只会在类或框架内部使用,而无须对外公布。这样一来,类的使用者有时可能会在查看回溯信息时发现private一词,从而知道不应该直接调用此方法了。这可算作一种编写"自我描述式代码"(self-documenting code)的办法。
在编写准备分享给其他开发者使用的程序库时,可以考虑创建Private分类。经常会遇到这样一些方法: 它们不是公共API的一部分,然而却非常适合在程序库之内使用。此时应该创建Private分类,如果程序库中的某个地方要用到这些方法,那就引入此分类的头文件。而分类的头文件并不随程序库一并公开,于是该库的使用者也就不知道库里面还有这些私有方法了。

要点

  • 使用分类机制把类的实现代码划分成易于管理的小块
  • 将应该视为"私有"的方法归入名叫Private的分类中,以隐藏实现代码
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 第一,先慢下来。不是不做,而是想清楚了再去做。� 先把心静下来,看清楚了才能想清楚,想清楚再去做。 在这个时代仅凭...
    玉米南瓜胡萝卜阅读 542评论 0 0
  • 鬓影重叠远,香风卷弱襟。 小轩窗一敞偷心。 春色如今方好,争用自持矜? 此景缘应短,何须挽留频? 又听闻远处拂琴。...
    陈婉_阅读 259评论 1 0
  • 【2017年03月01星期三——咖啡冥想】 财富目标:我近期最想实现的一个愿望或目标是:保险每个月业绩增长2倍,收...
    热瓦昆空阅读 205评论 1 2