类簇
类簇是一种Foundation框架经常使用的设计模式(desigen pattern)。类簇聚合类在一个共有抽象基类下的一些私有的具体的子类。用这种方法组合的类简化了面向对象(object-oriented)框架的共有的可用的结构,同时并没有减少它的功能的丰富性,类簇是基于抽象工厂设计模式的 (Abstract Factory design pattern)。
为了说明(illustrate)类簇的体系结构和好处,考虑一个定义可存储不同类型数值的对象的类的结构层次的问题。因为不同类型的数值通常有很多特征(他们可以从一种类型转换成另一种类型,例如,他们可以被描述成一个字符串),所以他们能被一个简单的类所描述。然而对他们存储的需求确是不同的,所以通过相同类去表示他们是没有用的。考虑到这个事实,图1-1描述一种可以解决类的结构层次的设计。
图 1-1Number是一个抽象的基类,声明的方法一般都是操作它的子类。然而,他不声明一个实例变量去存储一个数值。子类声明这样的实例变量并且在Number中声明的编程接口中共享它。
所以,这种设计是相当的简单。然而,如果把基本的常用的C语言类型的考虑在内,类的层次图应如图1-2所示。
图 1-2简单的概念(创建一个类去存储数值)可以在一些类下很容易的发散。类簇结构呈现了反应了概念简单的设计。
应用类簇设计模式这个问题收益率类层次结构如图1 - 3所示。
这个层次的用户仅看到了一个公共类,所以他是如何创建合适的子类对象的呢?答案是以抽象超类的方式处理实例化。
创建实体
在类簇中的抽象基类必须声明创建它的私有子类实体的方法。基类的责任是分配合适的子类对象。
在Foundation框架中。你通常通过调用+className、alloc、init方法创建对象。以Foundation框架的NSNumber为例,你可以发送这些消息去创建一个数值对象:
NSNumber *aChar = [NSNumber numberWithChar:’a’]; |
NSNumber *anInt = [NSNumber numberWithInt:1]; |
NSNumber *aFloat = [NSNumber numberWithFloat:1.0]; |
NSNumber *aDouble = [NSNumber numberWithDouble:1.0]; |
你没必要释放通过工厂方法返回的对象。很多类也提供了标准的alloc、init方法创建对象,这需要你管理他们的存储单元的分配。
生成每个对象可能属于不同子类(事实上就是如此)。虽然每个对象的类成员是隐藏的,但是被抽象基类声明的接口却是是公共的。虽然不是十分正确,但是把 aChar、anInt、aFloat、aDouble对象当作NSNumber类的对象是很方便的。因为他们是通过NSNumber的类方法创建的,并且通过NSNumber声明的方法访问实例变量。
- (NSString *)title;
这个书对象能够返回它本身的实例变量或创建一个新的字符串对象并且返回他,这都不中重要。从这个声明中清楚的表明返回的字符串不能被修改。任何视图对返回对象的改变将引发编译警告。
在类簇中创建子类类簇架构涉及简单性和可扩展性之间的权衡:用一些公有类代替很多私有类简化了框架中类的学习和使用,但是创建任何类簇的子类变得困难。然而,如果它很少需要创建一个子类,那么类簇架构显然是很有益的。类簇在Foundation框架就是这种情况。
如果你发现类簇没有提供你的问题需要的方法,这时可能就需要一个子类。例如想象一下你需要创建一个文件存储而不是内存存储的数组对象,就像NSArray类簇一样。因为你改变类的底层存储机制,您必须创建一个子类。
另一方面,在某些情况下,可能就足够了(容易)定义一个类,在其中嵌入对象从集群。假设您的程序需要修改一些数据时提醒你。在这种情况下,创建一个简单的类,将一个数据对象的基础框架定义可能是最好的方法。这个类的对象可以介入的消息修改数据,拦截信息,代理,然后转发给嵌入式数据对象。
总之,如果你需要管理你的对象的存储,创建一个真正的子类。否则,创建一个复合对象,一个标准的基础框架对象嵌入在你自己的设计的一个对象。以下部分提供更详细地对这两种方法:
方法一:
创建真正的子类。
以一个类簇创建一个新类你需要:
1、继承自类簇的虚基类
2、定义自己的存储
3、重写父类的所以初始化方法
4、重写父类的原始方法
因为类簇的虚基类在类簇中仅有的公共可访问的点(系统定义的他的子类不可见),所以第一点是显而易见的。这意味着新的子类将继承类簇的接口却不能继承其实例变量,因为虚基类中没有定义。第二点:子类必须声明自己需要的实例变量。最后:子类必须重写继承的所有方法,来访问一个对象的实例变量。这些方法叫做基元方法。
一个类的基元方法来自他的接口的底层。以NSArray为例,它声明了管理数组对象的接口。理论上,一个数组对象存储一些数据元,每个数据元可以通过下标访问到。NSArray表现这概念通过两个基元方法:count和objectAtIndex:。用这两个方法作为基础,其他的导出方法都可以实现。表1-2给出了用两个基元方法实现的导出方法。
导出方法和他们可能的实现 Derived Method
Possible Implementation
lastObject
通过
[self objectAtIndex: ([self count] –1)]
这个消息找到最后一个元素
containsObject:
反复的向数组对象发送
objectAtindex:
消息找到一个元素, 每次增加下标知道所有的元素被试过一次将接口区分成基元方法和导出方法使得创建子类更容易。你必须重写继承的基元方法,但做到了就可以确保它继承的所有派生方法正常运转。
通常,类簇的虚基类回声明一些init和+className方法。虚类依据你选择init方法还是+className方法决定那个实体类去实例化。你可以这样认为虚类声明这些方法是为了使用子类的便利性。虚类没有实例变量,所以不需要初始化方法。
你的子类应该声明他自己的init、+className方法(如果他需要初始化他自己的实例变量),他不应依赖以继承来的方法。为了保持初始化链的链接,它应该在自己指定的初始化方法中调用基类的指定初始化器。他应该重写所有的其他继承的初始化方法并且实现他们,让他们有一个合理的行为方式。在一个类簇中,指定初始化方法一直是init方法。
实例:
假定你想穿件一个NSArray的子类叫做MonthArray,他会返回一个月份的名字通过给定的位置坐标。然而,MonthArray对象不会真正存储一组月份的名字作为实例变量。相反,该方法返回给定下标位置的常量字符串。这样,仅需要创建12个字符串对象,不用关心有多少MonthArray对象纯在雨应用中。
MonthArray类声明如下
#import <foundation/foundation.h> @interface MonthArray : NSArray { } + monthArray; - (unsigned)count; - (id)objectAtIndex:(unsigned)index; @end注:MonthArray类不需要声明init方法,以为他没有实例变量。count和objectAtIndex:方法仅重写了继承的基元方法。
MonthArray类
的实现如下:
#import "MonthArray.h" @implementation MonthArray static MonthArray *sharedMonthArray = nil; static NSString *months[] = { @"January", @"February", @"March", @"April", @"May", @"June", @"July", @"August", @"September", @"October", @"November", @"December" }; + monthArray { if (!sharedMonthArray) { sharedMonthArray = [[MonthArray alloc] init]; } return sharedMonthArray; } - (unsigned)count { return 12; } - objectAtIndex:(unsigned)index { if (index >= [self count]) [NSException raise:NSRangeException format:@"***%s: index (%d) beyond bounds (%d)", sel_getName(_cmd), index, [self count] - 1]; else return months[index]; } @end
因为MonthArray重写了继承的基元方法,所以他继承的导出方法及使没有重写也会正常工作。
NSArray的
lastObject
,containsObject:
,sortedArrayUsingSelector:
,objectEnumerator
, 和其他方法在MonthArray
对象中也会没有问题的工作.方法二:
类的组合
通过植入一个私有的类簇对象在一个你自己设计的对象中,你创建了一个组合复合对象。这个复合对象可以依靠类簇对象的基本功能,用特定的方式截取复合对象想处理的消息。这种结构减少了很多代码并且允许您利用已经测试过的Foundation框架所提供的代码。图1-4描述了这种结构。
图 1-4一个嵌入类簇对象的对象
复合对象必须声明他自己继承自类簇的虚基类。最为一个子类,他必须重写基类的基元方法,也可以重写导出方法,但是那不是必须的,因为导出方法是通过调用基元方法实现的。NSArray类的count方法就是一个例子,中间的对象的实现如下:
- (unsigned)count { return [embeddedObject count]; }However, your object could put code for its own purposes in the implementation of any method it overrides.
然而,对象可以把代码实现自己的目的的任何方法重写。
实例
为了说明复合对象的应用,想象你需要一个可变的数组对象,测试变化对一些验证标准允许任何修改数组的内容。后面的示例描述了一个类称为ValidatingArray,其中包含一个标准的可变数组对象。ValidatingArray覆盖所有的原始方法中声明它的超类,NSArray NSMutableArray。也声明数组、validatingArray和init方法,可以用来创建并初始化一个实例:
#import <foundation/foundation.h> @interface ValidatingArray : NSMutableArray { NSMutableArray *embeddedArray; } + validatingArray; - init; - (unsigned)count; - objectAtIndex:(unsigned)index; - (void)addObject:object; - (void)replaceObjectAtIndex:(unsigned)index withObject:object; - (void)removeLastObject; - (void)insertObject:object atIndex:(unsigned)index; - (void)removeObjectAtIndex:(unsigned)index; @end实现文件显示,在一个init方法ValidatingArrayclass,嵌入式对象创建和分配给embeddedArray变量。消息只是访问数组但不要修改其内容传送到嵌入的对象。可以改变内容审查的消息(在伪代码)和传递只有通过假设的验证测试。
#import "ValidatingArray.h" @implementation ValidatingArray - init { self = [super init]; if (self) { embeddedArray = [[NSMutableArray allocWithZone:[self zone]] init]; } return self; } + validatingArray { return [[[self alloc] init] autorelease]; } - (unsigned)count { return [embeddedArray count]; } - objectAtIndex:(unsigned)index { return [embeddedArray objectAtIndex:index]; } - (void)addObject:object { if (/* modification is valid */) { [embeddedArray addObject:object]; } } - (void)replaceObjectAtIndex:(unsigned)index withObject:object; { if (/* modification is valid */) { [embeddedArray replaceObjectAtIndex:index withObject:object]; } } - (void)removeLastObject; { if (/* modification is valid */) { [embeddedArray removeLastObject]; } } - (void)insertObject:object atIndex:(unsigned)index; { if (/* modification is valid */) { [embeddedArray insertObject:object atIndex:index]; } } - (void)removeObjectAtIndex:(unsigned)index; { if (/* modification is valid */) { [embeddedArray removeObjectAtIndex:index]; } }