#objective-c #variables #inheritance #methods #init
#objective-c #переменные #наследование #методы #инициализация
Вопрос:
Я читаю книгу Objective-C, и у меня есть вопрос, на который книга, похоже, не дает ответа.
Допустим, у меня есть два пользовательских класса.
Вызывается первый класс ClassA
. Конечно, в нем есть файлы .h и .m. Вызывается второй класс ClassB
. У него также есть и то, и другое.файлы h и .m.
Где-то в коде ‘ClassA’ есть этот метод:
-(IBAction)displaySomeText:(id)sender {
ClassB *myNumber = [[ClassB alloc]init];
NSString *numberString = [myNumber storedNumberAsString];
// storedNumberAsString is just a method that returns a string object that holds
// myVariable.
[textView insertText:numberString];
//textView is a object I created that just displays some text on screen.
[myNumber release];
}
В книге говорится, что у этого ClassB
должен быть метод:
-(id)init {
[super init]; //I know why this is done, the book explains it well.
myVariable = 42; // I created this variable already in the ClassB .h file
return self;
}
Теперь, когда в построителе интерфейса я нажимаю кнопки, которые я подключил, и т. Д. Это работает, отображаемое число есть 42
.
Мой вопрос в том, почему я должен создавать -(id)init
метод для ClassB
, если я могу сделать следующее в ClassA
методе:
-(IBAction)displaySomeText:(id)sender {
ClassB *myNumber = [[ClassB alloc]init];
myNumber.myVariable = 42; //I just do this to skip the -(id)init method.
NSString *numberString = [myNumber storedNumberAsString];
[textView insertText:numberString];
[myNumber release];
}
При этом он по-прежнему отображает то же значение : 42
. Я могу изменить его на то, что мне нравится. Так почему бы просто не использовать init
унаследованный from NSObject
и просто сделать простой способ myNumber.myVariable = 42
?
Комментарии:
1. Я думаю, что это просто плохой пример использования init
Ответ №1:
Предположим, что значение переменной экземпляра было чем-то более сложным, чем целое число. Предположим, что это связано с чтением строки из файла, или получением некоторой информации по сети, или просто выполнением некоторых арифметических действий. В этом случае не имело бы смысла нести ClassA
ответственность за правильную установку этого значения. Это нарушило бы инкапсуляцию, которая в первую очередь делает полезным иметь отдельные классы.
В этом чрезвычайно простом случае вы совершенно правы, возможно, нет причин иметь пользовательский инициализатор ClassB
, но в целом класс сам должен отвечать за правильную настройку своего состояния. Возложение этой ответственности на другие классы означает, что эти другие должны знать о внутренних компонентах первого, что означает, что они могут быть слишком тесно связаны.
В некоторых случаях значение ivar может быть частью информации, которая известна только ClassA
или должна быть вычислена на основе такой части информации. Затем вы должны создать пользовательский инициализатор, для ClassB
которого будет получено это значение, например, - (id) initWithInteger:
это станет «назначенным инициализатором», и затем вы переопределите -[ClassB init]
его вызов с некоторым разумным значением по умолчанию.
Комментарии:
1. Спасибо. Имеет такой смысл. Я должен был спросить, я не был уверен, и когда я изучаю язык, я хочу охватить все основы того, почему все делается так, как есть.
2. Рад, что это помогло. Что-нибудь, что вам еще нужно заполнить? Я написал этот ответ довольно быстро; возможно, его можно было бы расширить.
Ответ №2:
Если экземпляры ClassB не должны иметь ничего инициализированного (кроме nil / zero), вам не нужно создавать явный init
метод для ClassB. В этом случае вопрос заключается в том, является ли установка myVariable
в 42
ответ ClassB на жизнь, вселенную и все остальное, или myVariable
это просто поле в ClassB, которому можно присвоить любое значение.
То есть проблема носит концептуальный, а не физический характер. Если концептуально значение 42
«принадлежит» ClassB , то для ClassB должен быть init
метод, который его устанавливает. Если это конкретное значение имеет большее значение для ClassA, чем для ClassB, то его должен установить какой-либо метод ClassA. Если вы делаете это «неправильно», код все равно работает нормально, но ваш дизайн немного менее элегантный, немного менее расширяемый, немного менее надежный.
Ответ №3:
Это довольно сложная проблема. Я был «воспитан» думать, что после запуска конструктора (инициализатора) объект должен быть готов к работе. Вы должны иметь возможность безопасно вызывать для него любой метод. Поэтому вам необходимо настроить любые переменные экземпляра в конструкторе, для которых 0 не является допустимым значением. Мне нравится настраивать их, если они в любом случае имеют значения 0, просто для здравомыслия, потому что я никогда не хочу беспокоиться о том, чтобы знать мельчайшие детали каждого языка, с которым я работаю, например, автоматически ли они инициализируют переменные экземпляра в 0.
Однако есть некоторые аргументы в пользу того, чтобы не инициализировать некоторые переменные.
-
Инициализация сложна, например, загрузка файла или получение данных из сети. Вы хотите сохранить открытой возможность создания экземпляра и ожидания, пока вы не будете готовы выполнять операции с большим весом.
-
Существует довольно много переменных экземпляра, которые можно настраивать. Ваши варианты — создать конструктор с множеством аргументов или создать конструктор без аргументов или с несколькими аргументами, и пусть вызывающий объект решает, какие значения должны быть установлены в значения, отличные от значений по умолчанию, установщиками свойств.
-
Вам нужно настроить весь граф объектов, прежде чем вы сможете осмысленно инициализировать значение. То есть инициализация значения может иметь побочные эффекты, которые зависят от других связанных объектов. Лучшее решение — создать каждый объект, затем использовать установщики свойств для установки отношений между объектами, а затем использовать установщики свойств для инициализации значений атрибутов.