views:

3504

answers:

1

Hello! I have got two classes, one a subclass of the other (say Animal and Dog). The superclass has got some initializers (say initAnimal), the subclass has some initializers (say initDog). The problem is that it is perfecly legal (from the compiler’s viewpoint) to do something like Dog *adog = [[Dog alloc] initAnimal], ie. initialize a class using its superclass initializer. I do not like this, because the subclass can have some extra instance variables that I want to make sure are initialized. A look into the header file solves this, but is there a simple way to make the compiler check for me? I have got a feeling I am missing something terribly obvious, but I just can’t put my finger on it :-)

Update: The initDog and initAnimal were not the best examples. I meant two really different initializers (like init for Animal and initWithFur for Dog). If I wanted every dog to have some fur assigned, I would have made the fur part of the initializer, so that nobody could obtain a dog object without a fur. But then it’s still easy to mistakenly initialize the instance with the superclass init, and then I’m hosed.

Thanks for bringing up the designated initializers, Jason. It did not occur to me before, but I could overload the designated initializer of the superclass and set some sane defaults there. But I would still prefer if I could somehow make it illegal to use other initializers than those of the class itself – any more ideas?

+7  A: 

Generally in Objective-C you create a designated initializer for each class and then subclasses use the same initializer. So instead of using initAnimal and initDog, you just use init. The dog subclass would then define its own init method and call the designated initializer in its parent class:

@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

You don't really have to specify initDog and initAnimal because the class is declared on the right hand side of the assignment...

Update: I'm adding the following to the answer to reflect the additional information in the question

There are a number of ways to ensure that subclasses don't call initializers other than their designated initializer and the way you ultimately choose will be largely based on your whole design. One of the nice things about Objective-C is that it's so flexible. I will give you two examples here to get you started.

First, if you create a subclass that has a different designated initializer than its parent class, you can overload the parent's initializer and throw an exception. This will let programmers know immediately that they've violated the protocol for your class... however, it should be stated that you should have a very good reason for doing this and that it should be very well documented that the subclass may not use the same initializer as the superclass.

@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

Another way to do it is to have initializer more like your original example. In that case, you could change the default init on the parent class to always fail. You could then create a private initializer for your parent class and then make sure everyone calls the appropriate initializer in subclasses. This case is obviously more complicated:

@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

Here you see the interface and implementation of the Animal and Dog class. The Animal is the designated top-level object and therefore overrides NSObject's implementation of init. Anyone who calls init on an Animal or any of Animal's subclasses will get an assertion error referring them to the documentation. Animal also defines a private initializer on a private category. The private category would stay with your code and subclasses of Animal would call this private initializer when they call up to super. It's purpose is to call init on Animal's superclass (NSObject in this case) and to do any generic initialization that might be necessary.

Finally, the first line in Animal's initAnimal method is an assertion that the receiver is actually an Animal and not some subclass. If the receiver is not an Animal the program will fail with an assertion error and the programmer will be referred to the documentation.

These are just two example of how you might design something with your specific requirements. However, I would strongly suggest you consider your design constraints and see if you really require this type of design as it's non-standard in Cocoa and in most OO design frameworks. For instance, you may consider making various animals root-level objects and just have an Animal protocol instead, requiring that all of the various "animals" respond to certain animal-generic messages. That way each animal (and true subclasses of Animal) can handle their designated initializers themselves and won't have to rely on superclasses behaving in such a specific, non-standard manner.

HTH, Jason

Jason Coco
Very nice, thank You for Your time.
zoul