使用超类初始化程序初始化类

时间:2008-11-10 09:59:16

标签: objective-c oop inheritance initialization

我有两个类,一个是另一个的子类(比如AnimalDog)。超类有一些初始化器(比如initAnimal),子类有一些初始化器(比如initDog)。问题是,从Dog *adog = [[Dog alloc] initAnimal]开始,完全合法(从编译器的角度来看),即。使用超类初始化程序初始化类。我不喜欢这个,因为子类可以有一些我想确保初始化的额外实例变量。查看头文件可以解决这个问题,但有一种简单的方法可以让编译器检查一下吗?我有一种感觉,我错过了一些非常明显的东西,但我不能把手指放在它上面: - )

更新: initDoginitAnimal不是最好的例子。我的意思是两个非常不同的初始值设定项(例如init的{​​{1}}和Animal的{​​{1}}。如果我希望每只狗都分配一些毛皮,我会将毛皮作为初始化器的一部分,这样就没有人可以获得没有皮毛的狗对象了。但是,使用超类initWithFur错误地初始化实例仍然很容易,然后我就被软管了。

感谢您提出指定的初始化程序,杰森。之前我没有想过,但我可以重载超类的指定初始化程序并在那里设置一些合理的默认值。但是我仍然会更喜欢,如果我能以某种方式使用其他初始化器而不是类本身的那些 - 更多的想法?

1 个答案:

答案 0 :(得分:19)

通常在Objective-C中,您为每个类创建一个指定的初始值设定项,然后子类使用相同的初始值设定项。因此,您只需使用init,而不是使用initAnimal和initDog。然后,dog子类将定义自己的init方法,并在其父类中调用指定的初始化程序:

@implementation Dog
-(id)init
{
    if( (self = [super init]) ) {  // call init in Animal and assign to self
        // do something specific to a dog
    }
    return self;
}
@end

您实际上不必指定initDog和initAnimal,因为该类是在赋值的右侧声明的......

更新:我在答案中添加以下内容以反映问题中的其他信息

有许多方法可以确保子类不会调用除指定初始化程序之外的初始化程序,并且最终选择的方式将主要基于整个设计。 Objective-C的一个好处是它非常灵活。我会在这里给你两个例子来帮助你。

首先,如果您创建的子类具有与其父类不同的指定初始值设定项,则可以重载父级的初始值设定项并抛出异常。这会让程序员立刻知道他们违反了你班级的协议......但是,应该说明你应该有一个非常这样做的理由并且它应该很好记录了子类可能不使用与超类相同的初始化器。

@implementation Dog
-(id)init
{
    // Dog does not respond to this initializer
    NSAssert( false, @"Dog classes must use one of the designated initializers; see the documentation for more information." );

    [self autorelease];
    return nil;
}

-(id)initWithFur:(FurOptionsType)furOptions
{
    if( (self = [super init]) ) {
        // do stuff and set up the fur based on the options
    }
    return self;
}
@end

另一种方法是让初始化程序更像原始示例。在这种情况下,您可以将父类的默认init更改为始终失败。然后,您可以为父类创建一个私有初始化程序,然后确保每个人都在子类中调用适当的初始化程序。这个案子显然更复杂:

@interface Animal : NSObject
-(id)initAnimal;
@end

@interface Animal ()
-(id)_prvInitAnimal;
@end

@interface Dog : Animal
-(id)initDog;
@end

@implementation Animal
-(id)init
{
    NSAssert( false, @"Objects must call designated initializers; see documentation for details." );

    [self autorelease];
    return nil;
}

-(id)initAnimal
{
    NSAssert( [self isMemberOfClass:[Animal class]], @"Only Animal may call initAnimal" );

    // core animal initialization done in private initializer
    return [self _prvInitAnimal];
}

-(id)_prvInitAnimal
{
    if( (self = [super init]) ) {
        // do standard animal initialization
    }
    return self;
}
@end

@implementation Dog
-(id)initDog
{
    if( (self = [super _prvInitAnimal]) ) {
        // do some dog related stuff
    }
    return self;
}
@end

在这里,您可以看到Animal and Dog类的界面和实现。 Animal是指定的顶级对象,因此会覆盖NSObject的init实现。任何在Animal或任何Animal的子类上调用init的人都会收到断言错误,并将其引用到文档中。 Animal还在私有类别上定义私有初始值设定项。私有类将保留您的代码,Animal的子类在调用super时会调用此私有初始化程序。它的目的是在Animal的超类(在本例中为NSObject)上调用init,并进行可能需要的任何通用初始化。

最后,Animal的initAnimal方法的第一行是断言接收者实际上是动物而不是某个子类。如果接收者不是Animal,则程序将因断言错误而失败,程序员将被引用到文档中。

这些只是您可以根据具体要求设计某些内容的两个示例。但是,我强烈建议您考虑您的设计约束,看看您是否真的需要这种类型的设计,因为它在Cocoa和大多数OO设计框架中都是非标准的。例如,您可以考虑制作各种动物根级对象,而只需要一个动物协议,要求所有各种“动物”对某些动物通用信息做出反应。这样每个动物(以及Animal的真正子类)都可以自己处理它们的指定初始化器,并且不必依赖于以这种特定的非标准方式运行的超类。