创建具有不同子类属性的子类。

8

假设我有这个类:

@interface CustomClass : NSObject

@property (nonatomic, strong) NSArray * nicestArrayEver;

@end

我想创建一个CustomClass的子类,但有一个问题

@interface ASubClassCustomClass : CustomClass

@property (nonatomic, strong) NSMutableArray * nicestArrayEver;

@end

问题如您所想象的那样,当我初始化ASubClassCustomClass并调用它的超级初始化程序(因为还需要其他属性)时,inmutable nicestArrayEver被创建了..我该如何避免它的创建,以便我可以设置可变的数组?

注:这只是一个示例,实际实现调用一个很难创建并且真正定制的子类(不是NSArray)。

4个回答

6
你可以通过使用不同的后备变量来使其工作,在合成时看起来像这样:@synthesize nicestArrayEver = nicestArrayEverSubClass_;
#import <Foundation/Foundation.h>

@interface CustomClass : NSObject

@property (nonatomic, strong) NSArray * nicestArrayEver;

@end

@implementation CustomClass
@synthesize nicestArrayEver ;

-(id)init
{
    if (self = [super init]) {
        nicestArrayEver = [[NSArray alloc] init];
    }
    return self;
}
@end

@interface ASubClassCustomClass : CustomClass

@property (nonatomic, strong) NSMutableArray * nicestArrayEver;

@end

@implementation ASubClassCustomClass
@synthesize nicestArrayEver = nicestArrayEverSubClass_;

-(id)init{
    if (self = [super init]) {
        nicestArrayEverSubClass_ = [[NSMutableArray alloc] init];
    }
    return self;
}
@end



int main(int argc, const char * argv[])
{

    @autoreleasepool {

        CustomClass *c1 = [[[CustomClass alloc] init] autorelease];
        ASubClassCustomClass *c2 = [[[ASubClassCustomClass alloc] init] autorelease];

        NSLog(@"%@", NSStringFromClass([[c1 nicestArrayEver] class]));
        NSLog(@"%@", NSStringFromClass([[c2 nicestArrayEver] class]));

    }
    return 0;
}

输出

2012-05-27 01:59:16.221 NicestArray[2312:403] __NSArrayI
2012-05-27 01:59:16.225 NicestArray[2312:403] __NSArrayM

另一种方法是在基类中有两个init方法,一个实例化属性,另一个不实例化属性,而是留给子类来完成这个任务。这将防止您创建昂贵的对象只是为了将它们丢弃。
现在基类可以直接使用第二个init实例化并进入“false”状态。您可以通过使用isMemberOfClass:检查self类类型,并在类类型为基类时抛出错误来避免这种情况。

@interface CustomClass : NSObject

@property (nonatomic, strong) NSArray * nicestArrayEver;
-(id)initWithoutArray;
@end

@implementation CustomClass
@synthesize nicestArrayEver ;

-(id) initWithoutArray
{
    if (self = [super init]) {
        if ([self isMemberOfClass:[CustomClass class]]) {
            [NSException raise:@"AbstractMethodCall" format:@"%@ should be called only from Subclasses of %@", NSStringFromSelector(_cmd), NSStringFromClass([self class])];
        }
    }
    return self;
}


-(id)init
{
    if (self = [super init]) {
        nicestArrayEver = [[NSArray alloc] init];
    }
    return self;
}
@end

@interface ASubClassCustomClass : CustomClass

@property (nonatomic, strong) NSMutableArray * nicestArrayEver;

@end

@implementation ASubClassCustomClass
@synthesize nicestArrayEver = nicestArrayEverSubClass_;

-(id)init{
    if (self = [super initWithoutArray]) {
        nicestArrayEverSubClass_ = [[NSMutableArray alloc] init];
    }
    return self;
}

@end



int main(int argc, const char * argv[])
{

    @autoreleasepool {

        CustomClass *c1 = [[[CustomClass alloc] init] autorelease];
        ASubClassCustomClass *c2 = [[[ASubClassCustomClass alloc] init] autorelease];

        NSLog(@"%@", NSStringFromClass([[c1 nicestArrayEver] class]));
        NSLog(@"%@", NSStringFromClass([[c2 nicestArrayEver] class]));

        //this works, as it is the subclass
        ASubClassCustomClass *shouldWork = [[[ASubClassCustomClass alloc] init] autorelease];

        // ouch!
        CustomClass *shouldCrash = [[[CustomClass alloc] initWithoutArray] autorelease];

    }
    return 0;
}

1
+1 回答问题并轻松证明我错了。 :) - Josh Hudnall

1

属性几乎不应该具有可变类型。如果它们这样做,那么调用者可以获取指针并在其背后改变对象的属性。如果属性应该是外部可变的,那么应该通过变异方法来实现。

记住,属性定义接口而不是实现。 @synthesize 可以从属性声明创建实现,但如果会产生错误的结果,则不应使用它。

因此,第一步是为您的类和子类定义接口,而不考虑实现。只有在了解接口应该是什么之后,才能设计每个接口的实现。

无论属性是否是外部可变的,支持实例变量可能需要是可变的。该类可能需要单纯地内部改变其自己的属性。

您可以使基类具有指定的初始化器,该初始化器将数组对象作为参数(类型为 id,以便子类的覆盖不必将其强制转换为 NSMutableArray*)。然后,“正常”的初始化器将使用该指定的初始化器来调用要使用的 NSArray。子类的指定初始化程序将调用超类的指定初始化程序,并传入要使用的 NSMutableArray

或者,基类初始化程序可以调用另一个方法来获取数组。基类实现将返回一个NSArray。子类可以重写该方法以返回一个NSMutableArray


1
我不明白你为什么想这样做,但我建议你按照以下步骤操作:在你的子类中声明一个单独的NSMutableArray属性(我们称之为nicestMutableArrayEver),并重写你的父类NSArray属性的getter方法,以返回可变数组实例。
- (NSArray *)nicestArrayEver {
    return [self nicestMutableArrayEver];
}

这样,每当您引用超类属性时,就可以获取回您的mutableArray。

祝好,


我不明白为什么有人不想这样做。CustomClass和ASubClassCustomClass之间的关系是从一个普通情况到更特殊情况的专业化,这是面向对象设计中非常典型的模式。还要注意,使用您的方法,需要经常进行强制转换以消除编译器警告。 - vikingosegundo

-1

你不能真正这样做。只需使用 NSMutableArray 创建CustomClass。您可以将它们创建为类型 id 并检查 isKindOfClass:,但那只是一种繁琐的方式,而不是真正必要的。

我只能想到两个原因来做你所要求的事情:

  1. 避免额外的 NSMutableArray 开销。
  2. 防止 CustomClass 修改数组内容,除非它是一个 ASubClassCustomClass

虽然这些是好目标,但在这种情况下,简化一点是值得的。


Josh,请看我刚刚添加的注释。 如果是NSArray,我同意你的观点,但在这种情况下不是,而且很重。 - Diego Torres
无论哪种方式,你仍然处于同一艘船上。重新检查设计可能会很值得。如果子类具有不同类型的信息,则可能值得将其存储在不同的变量中。如果您愿意,可以创建一个类方法,根据需要返回相应的变量,并在子类中覆盖它。 - Josh Hudnall
在子类中拥有一个与父类同名但类型不同的属性是完全可能的。唯一需要注意的是(除非更改了超类实例变量的可见性),支持实例变量的名称必须不同。请参阅https://dev59.com/mWTVa4cB1Zd3GeqP_hXa#10690692/或vikingosegundo对此问题的回答。 - jscs
@JacquesCousteau 是的,这对我来说完全是新的。我曾经相信那不是事实,但显然我错了。我改正我的观点。 - Josh Hudnall

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接