类族模式
“类族”是一种很有用的模式,可以隐藏“抽象基类背后的实现细节”。Objective-C的系统框架中普遍使用此模式。如果想创建一个按钮,UIKit中就有一个名为“UIButton”的类,想创建按钮,需要调用“类方法”
- (UIButton*)buttonWithType:(UIButtonType)type;
该方法返回的对象,其类型取决于传入的按钮类型。然而,不管返回什么类型的对象,他们都继承自同一个基类,UIButton。这么做的意义:UIButton类的使用者无需关心创建出来的按钮具体属于哪个子类,也不用考虑按钮的绘制方式等实现细节。
对于开始问题,可以把各种按钮的绘制逻辑放在一个类里,并根据按钮类型来切换:
- (void)drawRect:(CGRect)rect {
if (_type == TypeA) {
} else if (_type == TypeB) {
}
}
这样写现在看上去很简单,然而,若是需要依赖按钮类型来切换的绘制方式有很多种,那么久会变得很麻烦了。此时应该使用“类族模式”,该模式可以灵活对应多个类,将它们的实现细节隐藏在抽象基类后面,以保持接口简洁。用户无需创建子类实例,只需调用基类方法类来创建即可。
创建类族
假设有个雇员的类,每个“雇员”都有名字和薪水这两个属性。管理者可以命令其执行日常工作。可是各个雇员的工作内容却不同。
首先定义抽象基类
typedef NS_ENUM(NSUInteger, EOCEmployeeType) {
EOCEmployeeTypeDeveloper,
EOCEmployeeTypeDesigner,
EOCEmployeeTypeFinance,
}
@interface EOCEmployee : NSObject
@property (copy) NSString* name;
@property NSUInteger salary;
+ (EOCEmployee*)employeeWithType:(EOCEmployeeType)type;
- (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
每个实体子类都从基类继承而来,例如
@interface EOCEmployeeDelveloper : EOCEmployee
@end
@implementation EOCEmployeeDelveloper
- (void)doADayWork {
[self writeCode];
}
@end
基类实现了一个“类方法”,该方法根据待创建的雇员分类分配好对应的雇员类实例。这种“工厂模式”是创建类族的办法之一。
OC这门语言没办法指明某个基类是“抽象的”。于是,开发者通常会在文档中指明类的用法。这种情况下,基类接口一般都没有声明init的成员方法,这暗示该类的实例也许不应该由用户直接创建。
Cocoa中的类族
我们经常需要向类族中新增子实体,不过这么做需要留意,在Employee这个例子中,若是没有“工厂方法”的源代码,那就无法向其中新增雇员类别了。然而对于Cocoa中NSArray这样的类族磊说,还是有办法新增子类的,但是需要遵守几条原则。
- 子类应该继承自类族中的抽象基类。
- 子类应该定义自己的数据存储方式。
我们以为NSArray会为我们保存对象,但是要记住,NSArray本身只不过是包在其他隐藏对象外面的壳,他仅仅定义了所有数组都需要具备的一些接口。 - 子类应当复写超类文档中指明需要复写的方法。