Распространена ситуация, когда в коде метода экземпляра, определенного в классе, необходимо вызывать другой метод экземпляра, определенный в том же классе.

Мы еще не обсуждали, как это сделать. Метод вызывается путем отправки сообщения объекту; но чем в этой ситуации является объект? Ответ заключается в ключевом слове self. Вот простой пример:

@implementation MyClass
- (NSString*) greeting (
return 3"Goodnight, Grade!";
}
(NSString*) sayGoodnightGracie ( return [self greeting];
)
Send

Когда сообщение sayGoodnightGracie отправляется экземпляру класса MyClass, выполняется метод экземпляра sayGoodnightGracie. Он отправляет сообщение greeting для экземпляра self. В результате вызывается метод экземпляра greeting; он возвращает строку @"Goodnight, Gracie!", и эта строка затем возвращается методом sayGoodnightGracie.

Пример выглядит достаточно простым, и это действительно так. В реальной практике ваш код класса часто будет состоять из нескольких открытых методов экземпляра вместе с множеством других методов экземпляра, которые используют открытые методы. В классе одни методы экземпляров будут постоянно вызывать другие. Они будут делать это с помощью отправки сообщений для экземпляра self.

Тем не менее за этим простым примером стоит тонкий и важный механизм, который должен выяснить реальное значение ключевого слова self. На самом деле ключевое слово self не означает “в том же классе”. В конце концов, экземпляр — не класс. Но какой именно экземпляр? Это тот же экземпляр, которому было отправлено первоначальное сообщение, в результате обработки которого впервые появилось ключевое слово self.

Давайте более подробно рассмотрим, что происходит, когда мы создаем экземпляр класса MyClass и отправляем сообщение sayGoodnightGracie созданному экземпляру:

MyClass* thing - [MyClass new];
NSString* s - [thing sayGoodnightGracie];

Мы создаем экземпляр класса MyClass и присваиваем созданный экземпляр переменной thing. Затем мы отправляем сообщение sayGoodnightGracie переменной thing, т.е. экземпляру, который только что создали. Сообщение достигает получателя, и оказывается, что это — экземпляр класса MyClass. Конечно же, класс MyClass реализует метод экземпляра sayGoodnightGracie, и этот метод вызывается. Когда он начинает работу, в коде встречается ключевое слово self. Оно означает “экземпляр, которому первоначально было отправлено исходное сообщение”. Это экземпляр, на который указывает переменная thing. Так что теперь сообщение greeting отправляется указанному экземпляру (рис. 5.3).

Значение ключевого слова self

 Рис 5.3. Значение ключевого слова self

Этот механизм может показаться довольно сложным, учитывая, что получается интуитивно ожидаемый результат. Однако этот механизм должен быть сложным, чтобы всегда давать верный результат. Это в особенности очевидно в случае, когда в игру включаются суперклассы и класс перекрывает метод суперкласса.

Для иллюстрации предположим, что у нас есть класс Dog с методом экземпляра bark. Предположим также, что класс Dog имеет метод экземпляра speak, который просто вызывает метод bark. Теперь предположим, что у нас имеется подкласс Basenj i класса Dog , который замещает метод bark. Что произойдет, когда мы пошлем сообщение speak экземпляру Basen j i, как в примере 5.2?

 

Пример 5.2. Полиморфизм в действии

@implementation Dog
- (NSString*) bark {
return @"Woof!";
}
- (NSString*) speak {
return [self bark];
}
@end
(^implementation Basenji : Dog
- (NSString*) bark {
return @"H; // Пустая строка, Basenjis не лает
}
@end
//В некотором другом классе:
Basenji* b = [Basenji new];
NSString* s = [b speak];

Если ключевое слово self означает просто “тот же класс, где встречается данное ключевое слово”, то когда мы отправляем сообщение speak экземпляру Basen j i, дело заканчивается реализацией speak в классе Dog (поскольку speak реализован именно в нем), и после этого должен бы быть вызван метод bark класса Dog. Это было бы ужасно, так как убивало бы весь смысл замещения; мы бы получили 0 "Woof! ", что для Basen j i неверно. Но, к счастью, ключевое слово self означает иное. Оно работает с экземпляром, а не классом.

Вот что происходит на самом деле. Сообщение speak отправляется нашему экземпляру Basen j i, переменной b. Класс Basen j i не реализует метод speak, так что мы идем вверх по иерархии классов и обнаруживаем, что метод speak реализован в суперклассе Dog. Мы вызываем метод экземпляра speak класса Dog; метод speak приступает к работе, и тут встречается ключевое слово self. Оно означает “экземпляр, которому первоначально было послано исходное сообщение”. Этим экземпляром является наш экземпляр b класса Basen j i. Так что мы отправляем сообщение bark экземпляру b класса Basen j i. Класс Basen j i реализует метод экземпляра bark, так что мы находим и запускаем этот метод, и возвращаем пустую строку (рис. 5.4).

Конечно, если класс Basen j i не замещает метод bark, то когда сообщение bark посылается экземпляру Basen j i, мы вновь проходим вверх по иерархии классов, находим, что метод bark реализован в классе Dog, и вызываем его. Таким образом, благодаря способу работы ключевого слова self наследование корректно работает и при замещении, и при его отсутствии.

Если вы смогли понять этот пример, то понимаете, что такое полиморфизм. Механизм, который я только что описал, имеет решающее значение для полиморфизма и является основой объектно-ориентированного программирования. (Заметим, что сейчас я говорю об объектно-

ориентированном, а не объектно-основанном программировании, как в главе 2. Это потому, что, на мой взгляд, именно добавление полиморфизма делает объектно-основанное программирование объектно-ориентированным.)

Наследование классов, замещение, self и полиморфизм 

Рис. 5.4. Наследование классов, замещение, self и полиморфизм


 

 

 

Добавить комментарий