Class Clusters 类集群

类簇是基础框架广泛使用的一种设计模式。类集群组在一个公共抽象超类下的一些私有的具体子类。以这种方式分组的类简化了面向对象框架的公开可见的体系结构,而不降低其功能的丰富度。类集群是基于抽象工厂设计模式的。

没有类集群:简单的概念,但是复杂的接口。

为了说明类集群体系结构及其优点,请考虑构造一个类层次结构的问题,它定义对象存储不同类型的数据(char、int、float、double)。因为不同类型的数字有许多共同的特性(例如,它们可以从一种类型转换为另一种类型,可以用字符串表示),它们可以由单个类表示。但是,它们的存储需求不同,因此用同一个类来表示它们是低效的。考虑到这个事实,我们可以设计图1-1所示的类架构来解决这个问题。

Class Clusters 类集群_第1张图片
一个简单的数字类的层次结构

Number是一个抽象的超类,它在它的方法中声明它的子类的通用操作。但是,它没有声明一个实例变量来存储数字。子类声明这样的实例变量,并共享由Number声明的编程接口。

到目前为止,这个设计相对简单。但是,如果考虑到这些基本C类型的常用修改,那么类层次图看起来更像图1-2。

Class Clusters 类集群_第2张图片
一个更完整的数字类层次结构。

简单的概念——创建一个类来容纳数字值——可以很容易地在十多个类中被激活。类集群体系结构提供了一种反映概念简单性的设计。

具有类集群:简单的概念和简单的接口。

将类集群设计模式应用到这个问题中,会产生图1-3中的类层次结构(私有类是灰色的)。

Class Clusters 类集群_第3张图片
类集群架构应用于数字类。

这个层次结构的用户只看到一个公共类,Number,那么如何分配适当子类的实例呢?答案是抽象的超类处理实例化。

创建实例

类集群中的抽象超类必须声明方法来创建其私有子类的实例。基于您所开发的创建方法来分配适当子类的对象,这是超类的责任,您不能,也不能选择实例的类。

在Foundation框架中,通常通过调用+className…方法或alloc…和init…方法来创建对象。以基础框架的NSNumber类为例,您可以发送这些消息来创建number对象:

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声明的接口。虽然它不完全正确,但是可以方便地将aChar、anInt、aFloat和aDouble对象视为NSNumber类的实例,因为它们是由NSNumber类方法创建的,并通过NSNumber声明的实例方法访问。

具有多个公共超类的类集群

在上面的示例中,一个抽象公共类声明多个私有子类的接口。这是最纯粹意义上的类集群。有两个(或者更多)抽象的公共类可以声明集群的接口,这也是可能的,而且通常是可取的。这在基础框架中很明显,包括表中列出的集群。

Class cluster Public superclasses
NSData NSData
NSMutableData
NSArray NSArray
NSMutableArray
NSDictionary NSDictionary
NSMutableDictionary
NSString NSString
NSMutableString

这种类型的其他集群也存在,但是这些集群清楚地说明了两个抽象节点如何协作,将编程接口声明为类集群。在每个集群中,一个公共节点声明所有集群对象都可以响应的方法,而另一个节点声明的方法仅适用于允许修改其内容的集群对象。

该集群接口的这种分解有助于使面向对象框架的编程接口更具表达性。例如,假设有一个对象表示该方法的一本书:

- (NSString *)title;

book对象可以返回自己的实例变量,或者创建一个新的string对象,然后返回——这并不重要。从这个声明中可以清楚地看出,返回的字符串不能被修改。任何修改返回的对象的尝试都会引起编译器警告。

在类集群中创建子类

类集群架构涉及到简单性和可扩展性之间的权衡:有一些公共类支持大量的私有类,这使得在框架中学习和使用类变得更容易,但是在任何一个集群中创建子类都比较困难。但是,如果很少需要创建子类,那么集群架构显然是有益的。在这些情况下,在基础框架中使用集群。

如果您发现集群没有提供程序所需的功能,那么子类可能是有序的。例如,假设您希望创建一个数组对象,该对象的存储是基于文件的,而不是基于内存的,如NSArray类集群。因为您正在更改类的底层存储机制,因此必须创建一个子类。

另一方面,在某些情况下,定义一个从集群中嵌入对象的类可能是足够的(而且更容易)。假设您的程序需要在某些数据被修改时发出警报。在这种情况下,创建一个封装了基础框架定义的数据对象的简单类可能是最好的方法。这个类的对象可以干预修改数据的消息,拦截消息,对其进行操作,然后将它们转发到嵌入式数据对象。

总之,如果需要管理对象的存储,请创建一个真正的子类。否则,创建一个复合对象,它将一个标准的基础框架对象嵌入到您自己设计的对象中。下面几节将详细介绍这两种方法。

一个真正的子类

在类集群中创建的新类必须:

  • 成为集群抽象超类的子类
  • 声明自己的存储
  • 重写超类的所有初始化方法
  • 覆盖超类的原始方法(如下所述)

因为集群的抽象超类是集群层次结构中唯一公开可见的节点,所以第一点是很明显的。这意味着新的子类将继承集群的接口,但是没有实例变量,因为抽象的超类声明没有。因此,第二点:子类必须声明它需要的任何实例变量。最后,子类必须重写它继承的任何直接访问对象实例变量的方法。这种方法被称为原始方法。

类的原始方法是其接口的基础。例如,使用NSArray类,它向管理对象数组的对象声明接口。在概念中,数组存储了许多数据项,每个数据项都可以通过索引访问。NSArray通过其两种原始的方法,count和objectAtIndex表达了这个抽象概念:。以这些方法为基础,可以实现其他方法派生的方法;表1-2给出了两个派生方法的例子。

Table 1-2 派生的方法及其可能的实现

派生的方法 有可能的内部实现
lastObject 通过发送这个消息的数组对象来找到最后一个对象: [self objectAtIndex: ([self count] –1)].
containsObject: 通过反复向数组对象发送objectAtIndex: message,每次递增索引,直到数组中的所有对象都经过测试,找到一个对象。

原始和派生方法之间的接口的划分使得创建子类更加容易。
您的子类必须重写继承的原语,但是这样做可以确保所有继承的方法都能正常运行。

原始派生的区别适用于完全初始化对象的接口。也需要解决子类中init...方法如何处理的问题

通常,集群的抽象超类声明了一些init…和 +名称的方法。正如在创建实例中所描述的那样,抽象类决定根据您选择的init来实例化哪个具体子类…或+名称的方法。您可以考虑,抽象类声明这些方法是为了方便子类。由于抽象类没有实例变量,所以它不需要初始化方法。

您的子类应该声明它自己的init…(如果需要初始化它的实例变量)和可能的+类名方法。它不应该依赖于它所继承的任何一个。为了在初始化链中维护它的链接,它应该在自己的指定初始化方法中调用它的父类的指定初始化器。它还应该覆盖所有其他继承的初始化方法,并以合理的方式实现它们。(参见多个初始化器和指定初始化器的指定初始化器)。在类集群中,抽象超类的指定初始化器总是init。

真正的子类:一个例子

假设您想创建一个名为 MonthArray 的 NSArray子类,它返回给定索引位置的一个月的名称。然而,一个 MonthArray 对象实际上不会将月名数组存储为实例变量。相反,返回给定索引位置的名称的方法(objectAtIndex:)将返回常量字符串。因此,不管应用程序中存在多少个月数组对象,只会分配12个字符串对象。

MonthArray 类声明如下:

#import 
@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对象工作没有问题。

复合对象

通过在自己的设计对象中嵌入一个私有的集群对象,您可以创建一个复合对象。这个复合对象可以依赖于集群对象的基本功能,只是截取了复合对象想要以某种特定方式处理的消息。这种架构减少了您必须编写的代码量,并允许您利用基础框架提供的测试代码。下图描述了这个架构。

Class Clusters 类集群_第4张图片
嵌入集群对象的对象

复合对象必须声明为集群抽象超类的子类。作为一个子类,它必须覆盖超类的原始方法。它也可以重写派生的方法,但这不是必需的,因为派生的方法可以通过原始方法来工作。

NSArray类的 count方法就是一个例子;中介对象实现其覆盖的方法可以很简单:

- (unsigned)count {
    return [embeddedObject count];
}

但是,您的对象可以为它自己的目的设置代码,以实现它覆盖的任何方法。

一个复合对象:一个例子

为了说明复合对象的使用,假设您需要一个可变数组对象,该对象在允许对数组内容进行任何修改之前,对一些验证条件进行测试。下面的示例描述了一个名为ValidatingArray的类,它包含一个标准的可变数组对象。ValidatingArray覆盖了在其超类、NSArray和NSMutableArray中声明的所有原始方法。它还声明了array、validatingArray和init方法,这些方法可以用来创建和初始化一个实例:

#import 
 
@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

实现文件显示了如何在ValidatingArrayclass的init方法中创建嵌入的对象,并将其分配给嵌入的变量。简单地访问数组但不修改其内容的消息会传递给嵌入的对象。可以更改内容的消息会被仔细检查(在伪代码中),并且只有在它们通过假设验证测试时才会传递。

#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];
    }
}
免责声明:以上内容均来自官方文档 OC 编程概念 ,在 有道翻译 的基础上进行修改整理,仅用于个人学习。因个人水平有限,如果存在错误,请读者谅解并指出,非常感谢!

你可能感兴趣的:(Class Clusters 类集群)