Инициализация класса с использованием инициализатора суперкласса - PullRequest
7 голосов
/ 10 ноября 2008

У меня есть два класса, один подкласс другого (скажем, Animal и Dog). Суперкласс имеет несколько инициализаторов (скажем, initAnimal), подкласс имеет несколько инициализаторов (скажем, initDog). Проблема в том, что совершенно правильно (с точки зрения компилятора) делать что-то вроде Dog *adog = [[Dog alloc] initAnimal], т.е. инициализировать класс, используя его инициализатор суперкласса. Мне это не нравится, потому что у подкласса могут быть некоторые дополнительные переменные экземпляра, которые я хочу убедиться, что они инициализированы. Заголовок решает эту проблему, но есть ли простой способ заставить меня проверить компилятор? У меня такое чувство, что я упускаю что-то ужасно очевидное, но я просто не могу это понять: -)

Обновление: initDog и initAnimal не были лучшими примерами. Я имел в виду два действительно разных инициализатора (например, init для Animal и initWithFur для Dog). Если бы я хотел, чтобы у каждой собаки был свой мех, я бы сделал меховой частью инициализатора, чтобы никто не мог получить предмет собаки без меха. Но тогда все еще легко ошибочно инициализировать экземпляр с помощью суперкласса init, и тогда я пойду.

Спасибо, что вызвали назначенных инициализаторов, Джейсон. Это не приходило мне в голову раньше, но я мог перегрузить назначенный инициализатор суперкласса и установить там некоторые нормальные значения по умолчанию. Но я все же предпочел бы, если бы я мог каким-то образом сделать незаконным использование других инициализаторов, кроме тех, что указаны в самом классе, - есть еще идеи?

1 Ответ

19 голосов
/ 10 ноября 2008

Обычно в Objective-C вы создаете назначенный инициализатор для каждого класса, а затем подклассы используют один и тот же инициализатор. Таким образом, вместо использования initAnimal и initDog, вы просто используете init. Затем подкласс 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 и Dog. Animal является обозначенным объектом верхнего уровня и поэтому переопределяет реализацию init в NSObject. Любой, кто вызовет init для Animal или любого из подклассов Animal, получит ошибку подтверждения, ссылаясь на документацию. Animal также определяет частный инициализатор для частной категории. Закрытая категория останется с вашим кодом, а подклассы Animal будут вызывать этот закрытый инициализатор при вызове super. Его целью является вызов init для суперкласса Animal (в данном случае NSObject) и выполнение любой общей инициализации, которая может потребоваться.

Наконец, первая строка в методе Animal initAnimal - это утверждение, что получатель на самом деле является Animal, а не каким-то подклассом. Если получатель не является животным, программа завершится с ошибкой подтверждения, и программист будет обращаться к документации.

Это всего лишь два примера того, как вы можете спроектировать что-то с учетом ваших конкретных требований. Тем не менее, я бы настоятельно предложил бы вам рассмотреть ограничения вашего дизайна и посмотреть, действительно ли вам нужен этот тип дизайна, поскольку он нестандартен в Какао и в большинстве сред разработки OO. Например, вы можете рассмотреть возможность создания различных корневых объектов для животных и использовать вместо них протокол Animal, требующий, чтобы все различные «животные» отвечали на определенные сообщения, характерные для животных. Таким образом, каждое животное (и настоящие подклассы Animal) может самостоятельно обрабатывать назначенные им инициализаторы, и им не придется полагаться на суперклассы, ведущие себя таким специфическим, нестандартным образом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...