Программирование на Objective-C 2.0
Шрифт:
Ниже приводится полное определение для ClassB, где определяется один метод с именем printVar. @interface ClassB: ClassA -(void) printVar; @end (^implementation ClassB -(void) printVar { NSLog (@"x = %i", x); } @end
Метод printVar выводит значение переменной экземпляра х, и при этом мы не определяем никаких переменных экземпляра в ClassB. Поскольку ClassB является подклассом для ClassA, он наследует все переменные экземпляра из ClassA (в данном случае — только одну переменную). Это показано на рис. 8.3.
Рис. 8.2. Подклассы и суперклассы
Рис. 8.3.
(Конечно, на рис. 8.3 не показаны другие методы или переменные экземпляра, наследуемые из класса NSObject.)
Теперь рассмотрим, как это все сочетается в программе. Для краткости поместим все объявления и определения в один файл. // Простой пример наследования #import <Foundation/Foundation.h> // Объявление и определение ClassA @interface ClassA: NSObject { int x; } -(void) initVar; @end @implementation ClassA -(void) initVar { x = 100; } @end // Объявление и определение ClassB @interface ClassB : ClassA -(void) printVar; @end @implementation ClassB -(void) printVar { NSLog (@"x = %i", x); } @end int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; ClassB *b = [[ClassB alloc] init]; [b initVar]; // будет использовать унаследованный метод [b printVar]; // раскрывает значение х; [b release]; [pool drain]; return 0; }
Вывод программы 8.1 х = 100
Мы начинаем с определения b как объекта класса ClassB. После выделения памяти и инициализации b следует передача сообщения для применения к b метода initVar. Н о в определении класса ClassB мы не видим, что определен какой-либо метод. Метод initVar был определен в классе ClassA, а поскольку ClassA является родительским классом для ClassB, он может использовать все методы ClassA. Метод initVar является унаследованным методом по отношению к ClassB. Примечание. Методы alloc и init, которые мы все время используем, никогда не определялись, поскольку мы применяем их как унаследованные методы.
После отправки объекту b сообщения initVar вызывается метод printVar для вывода значения переменной экземпляра х. Результат вывода х = 100 подтверждает, что printVar смог получить доступ к этой переменной экземпляра, поскольку он тоже был унаследован.
Концепция наследования действует вниз по всей цепочке. Так, если определить новый класс с именем ClassC, родительским классом для которого является ClassB: @interface ClassC: ClassB; @end
то ClassC унаследует все методы и переменные экземпляра класса ClassB, который, в свою очередь, наследует методы и переменные экземпляра класса ClassA, который, в свою очередь, наследует методы и переменные экземпляра NSObject.
Каждый экземпляр класса получает свои собственные переменные экземпляра, даже если они наследуются. Это означает, что объект класса ClassC и объект класса ClassB будут иметь свои собственные отдельные переменные экземпляра. Поиск подходящего метода
Прежде чем передавать сообщение объекту, надо выбрать подходящий метод для применения к этому объекту. Здесь действуют совершенно простые правила. Сначала нужно проверить класс, которому принадлежит данный объект. Если метод с нужным именем определен в этом классе, применяется этот метод. Если это не так, проверяется родительский класс, и если в нем определен данный метод, то применяется этот метод. В противном случае поиск продолжается.
Проверка родительских классов выполняется до тех пор, пока не найдется класс, который содержит указанный метод, или метод не будет найден даже в корневом классе. В первом случае вы можете действовать дальше; во втором случае возникает проблема и генерируется сообщение, аналогичное следующему: warning: 'ClassB' may not respond to '-inity* (предупреждение: 'ClassB',
возможно, не отвечает ’-inity’)Это означает, что вы ошибочно пытаетесь передать сообщение с именем unity переменной типа ClassB. Компилятор указывает, что переменные этого типа «не знают», как реагировать на такой метод. Это было определено после проверки методов класса ClassB и методов его родительских классов вплоть до корневого класса (которым в данном случае является NSObject).
В некоторых случаях, когда метод не найден, никакое сообщение не генерируется. Это означает, что используется пересылка (forwarding), описание которой приводится в главе 9. 8.2. Расширение посредством наследования: добавление новых методов
Наследование часто используется для расширения класса. Предположим, требуется разработать несколько классов для работы с двумерными графическими объектами, такими как прямоугольник, окружность и треугольник. В данном случае нас интересуют только прямоугольники (rectangle). Вернемся к упражнению 7 главы 4 и начнем с секции @interface. @interface Rectangle: NSObject { int width; int height; } @property int width, height; -(int) area; -(int) perimeter; @end
У вас будут синтезируемые методы для задания ширины (width, w) и высоты (height, h) прямоугольника, а также возврата этих значений, и ваши собственные методы для вычисления его площади (area) и периметра (perimeter). Добавим метод, который позволит задавать ширину и высоту прямоугольника в одном сообщении: -(void) setWidth: (int) w andHeight: (int) h;
Предположим, что это объявление нового класса введено в файл с именем Rectangle.h. Файл секции implementation с именем Rectangle.m может выглядеть следующим образом. #import "Rectangle.h" @implementation Rectangle @synthesize width, height; -(void) setWidth: (int) w andHeight: (int) h width = w; height = h; -(int) area { return width * height; -(int) perimeter { return (width + height) * 2; } @end
Каждое определение метода достаточно очевидно. В программе 8.2 показана процедура main для тестирования. #import "Rectangle.h" #import <stdio.h> int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Rectangle *myRect = [[Rectangle alloc] init]; [myRect setWidth: 5 andHeight: 8]; NSLog (@"Rectangle: w = %i, h = %i", myRect.width, myRect.height); NSLog (@"Area = %i, Perimeter = %i", [myRect area], [myRect perimeter]); [myRect release]; [pool drain]; return 0; }
Вывод программы 8.2 Rectangle (прямоугольник): w = 5, h = 8 Area (площадь) = 40, Perimeter (периметр) = 26
Сначала выполняются выделение памяти и инициализация объекта myRect; затем задается его ширина (5) и высота (8). Это проверяется в первой строке вывода. Затем вычисляются площадь и периметр прямоугольника путем вызова с помощью сообщения, и возвращаемые значения передаются для вывода процедуре NSLog.
Для работы с квадратами (square) можно было бы определить новый класс с именем Square и определить в нем методы, аналогичные методам класса Rectangle. Можно также учесть, что квадрат является частным случаем прямоугольника, у которого равны ширина и высота.
Поэтому проще создать новый класс с именем Square и сделать его подклассом класса Rectangle. Это позволит использовать все методы и переменные класса Rectangle помимо ваших собственных. После этого достаточно добавить только методы задания определенного значения для стороны квадрата и считывания этого значения. В программе 8.3 показаны файлы секций interface и implementation для нового класса Square. #import "Rectangle.h" @interface Square: Rectangle -(void) setSide: (int) s; -(int) side; @end #import "Square.h" @implementation Square: Rectangle -(void) setSide: (int) s { [self setWidth: s andHeight: s]; } (int) side { return width; } @end