美文网首页
第四章 协议与分类—第24条:将类的实现代码分散到便于管理的数个

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

作者: CoderCurtis | 来源:发表于2017-04-26 11:09 被阅读11次

类中经常容易填满各种方法,而这些方法的代码则全部堆在一个巨大的实现文件里。有时这么做是合理的,因为即便通过重构把这个类打散,效果也不会更好。在此情况下,可以通过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的分类中,以隐藏实现代码

相关文章

网友评论

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

      本文链接:https://www.haomeiwen.com/subject/cvhizttx.html