//联系人:石虎QQ: 1224614774昵称:嗡嘛呢叭咪哄
一、Objective-C类族和工厂模式
在iOS的系统类库中也有一种方式使得开发者不必关注类中具体的存储实现,但可以根据不同需求场景创建出合适的对象来。比如Foudation中的NSArray、UIkit中的UIButton。
本文结合几种工厂设计模式的原理,对Objective-C类族的概念做一下简要的整理。
0.三种工厂
其实除了《Design Patterns》中提到的Factory Method和Abstract Factory,常提到的还有另一种工厂模式Simple Factory。
在简单工厂中,产品有一个统一的interface,而可以有不同implementation,同时有一个(通常是仅有一个)工厂对象。需要产品的时候,工厂会根据已有条件做switch,选择一种产品实现,构建一个实例出来。这种设计将产品的抽象和实现分离开来,可以针对统一的产品接口进行通信,而不必特意关注具体实现的不同。对于产品类别的扩充也是可以的,但每增加一个产品,都需要修改工厂的逻辑,有一定维护成本。
工厂方法更进一步,将工厂也抽象出来,进行接口、实现分离。这样具体工厂和具体产品可以对应着同时扩充,而不需要修改现有逻辑。当然,使用者也许在不同场景要在一定程度上自己对应的工厂选择(这个总要有人知道,不可避免)。
抽象工厂相对于工厂方法主要是对整个产品类的体系进行了横向扩充,构成一个更为完整的系统。
1. Objective-C的类族(Class Cluster)
做iOS开发的朋友们一定用过NSNumber的numberWith…方法。但大家有可能都不知道NSNumber这样的方法调用返回的不是NSNumber类本身的对象,这正是Objective-C类族的微妙之处。
如上图所示,Number的概念很大。而实际上NSNumber实际上是有很多隐藏的子类的,而我们通过NSNumber的numberWith…方法得到的对象正是其子类的对象,但对于使用者几乎可以不必知道这一点,只要知道它是一个NSNumber对象就OK了。
“Simple Concept and Simple Interface”,这正是苹果设计类族的初衷,也是类族的优点所在。可以想象我们要用整数作为参数拿到一个NSNumber对象和一个布尔值参数拿到的NSNumber对象是不同的,这略微有些类似于switch逻辑(虽然是通过不同的方法),根据不同的条件提供不同的子类对象,而这一切都集中声明在公共接口类NSNumber中。我们很容易联想到上面提到的Simple Factory(简单工厂)设计模式。
没错,与简单工厂类似,类族的一个缺点也显现出来,那就是已有的类族不好扩展。比如你想NSNumber再多支持一种情况,这个恐怕很难。好在这些系统的类库已经将大部分可能都做进去了,考虑得比较完善,通常你只是去用就可以了。
2.类族的子类扩展
了解了类族的概念,我们在实际开发当中也可以采用其方式,利用其优点。上面提到对已有类族进行子类扩展是很难的,但这不代表NSNumber、NSArray等类就没法继承了。他们还是可以有自定义的子类的。
既然要做类族的子类,就要做到:
·以公共“抽象”类为父类,比如NSNumber、NSArray等,而非其子类
·提供自定义存储
·重写(覆盖)父类所有初始化方法
·重写父类中“原始”方法
其中第二点最重要,系统的类族通常在父类中只是提供了各种方法声明,而自身并不提供存储,所以要自定义子类一定要提供自己的存储,一般情况下这也是自定义子类的意义所在。
重写初始化方法,要遵从Objective-C初始化链的规范。
而重写“原始”方法,这个要说一下。按照苹果的文档,和指定初始化方法形式类似,这些类里面的众多方法可以分为两类,“原始”方法和“衍生”方法。“原始”方法定义了这个类及对象的最基本行为,而“衍生”方法则基于这些“原始”方法进行更复杂逻辑的包装。所以,重写了“原始”方法,“衍生”方法也自然效果就不同了。
除了自定义子类外,苹果官方更建议开发者用组合的方式对类族类进行包装。
3.对象所属类的判断
有人会问,如果我没有特殊需求,不需要写NSArray、NSNumber的子类,是不是了解类族就没有多大意义了。这里记一下,通过了解类族概念,我们至少知道了,通过NSNumber得到的对象,不一定是(基本上就不会是)NSNumber类本身的对象。
可以试验下,通过[NSNumber numberWithInt:2]和[NSNumber numberWithBool:YES]得到的对象对应的类,一个是__NSCFNumber,另一个是__NSCFBoolean。
那么,如下这样的判断就不行了:
idmaybeAnArray=;
if([maybeAnArrayclass]==[NSArrayclass]){
//Willneverbehit
}
需要在适当的情况下选择使用isMemberOfClass和isKindOfClass。
“类族”(class cluster)是一种很有用的模式(pattern),可以隐藏“抽象基类”(abstract base class)背后的实现细节。Objective-C的系统框架中普遍使用此模式,比如iOS的用户界面框架(user interface framework)UIKit中就有一个名为UIButton的类,想创建按钮,需要调用下面这个“类方法”(class method):
+(UIButton*)buttonWithType:(UIButtonType)type;
该方法所返回的对象,其类型取决于传入的按钮类型(button type),然而,不管返回什么类型的对象,它们都继承自同一个基类:UIButton。这么做的意义在于:UIButton类的使用者无须关心创建出来的按钮具体属于哪个子类,也不用考虑按钮的绘制方式等实现细节,使用者只需明白如何创建按钮,如何设置像“标题”(title)这样的属性,如何增加触摸动作的目标对象等问题就好。
创建类族
现在举例来演示如何创建类族,假设有一个处理雇员的类,每个雇员都有“名字”和“薪水”这两个属性,管理者可以命令其执行日常工作,但是,各种雇员的工作内容却不同,经理在带领雇员做项目时,无须关心每个人如何完成其工作,仅需指示其开工即可。
首先要定义抽象基类:
typedef NS_ENUM(NSUInteger,EOCEmployeeType){
EOCEmployeeTypeDeveloper,
EOCEmployeeTypeDesigner,
EOCEmployeeTypeFinance,
};
@interface EOCEmployee : NSObject
@property(copy)NSString *name;
@property NSUInteger salary;
// Helper for creating Employee objects
+(EOCEmployee*)employeeWithType:(EOCEmployeeType)type;
// Make Employees do their respective day's work
-(void)doADaysWork;
@end
@implementation EOCEmployee
+(EOCEmployee*)employeeWithType:(EOCEmployeeType)type {
switch(type){
case EOCEmployeeTypeDeveloper:
return[EOCEmployeeDeveloper new];
break;
case EOCEmployeeTypeDesigner:
return[EOCEmployeeDesigner new];
break;
case EOCEmployeeTypeFinance:
return[EOCEmployeeFinance new];
break;
}
}
-(void)doADaysWork {
// Subclasses implement this.
}
@end
每个“实体子类”(concrete subclass)都从基类继承而来,例如:
@interface EOCEmployeeDeveloper : EOCEmployee
@end
@implementation EOCEmployeeDeveloper
-(void)doADaysWork {
[self writeCode];
}
@end
在本例中,基类实现了一个“类方法”,该方法根据待创建的雇员类别分配好对应的雇员类实例,这种“工厂模式”(Factory pattern)是创建类族的办法之一。
可惜Objective-C这门语言没办法指明某个基类是“抽象的”(abstract),于是,开发者通常会在文档中写明类的用法,这种情况下,基类接口一般都没有名为init的成员方法,这暗示该类的实例也许不应该由用户直接创建。
Cocoa里的类族
系统框架中有许多类族,大部分collection类都是类族,如NSArray,下面这种代码:
id maybeAnArray =;
if([maybeAnArray class]==[NSArray class]){
// Will never be hit
}
其中的if语句永远不可能为真。[maybeAnArray class]所返回的类绝不可能是NSArray类本身,因为由NSArray的初始化方法所返回的那个实例其类型是隐藏在类族公共接口(public facade)后面的某个内部类型(internal type)。
要判断出某个实例所属的类是否位于类族之中,应该使用类型信息查询方法(introspection method),不要直接检测两个“类对象”是否等同,而应该采用下列代码:
id maybeAnArray =;
if([maybeAnArray isKindOfClass:[NSArray class]]){
// Will be hit
}
谢谢!!!