Программирование на Objective-C 2.0
Шрифт:
Полиморфизм (polymorphism) позволяет разрабатывать программы таким образом, чтобы объекты из различных классов могли определять методы, которые имеют одно и то же имя. Динамический контроль типов (dynamic typing) позволяет откладывать определение класса, которому принадлежит объект, до выполнения программы. Динамическое связывание (dynamic binding) позволяет откладывать определение конкретного метода для вызова в объекте до начала выполнения программы. 9.1. Полиморфизм: одно имя, различные классы
В программе 9.1 показан файл секции interface для класса Complex, который используется для представления в программе комплексных чисел. Программа 9.1. Файл секции interface Complex.h // файл секции interface для класса Complex #import <Foundation/Foundation.h> @interface Complex: NSObject { double real; double imaginary; } @property double real, imaginary; -(void) print; -(void) setReal: (double) a andlmaginary: (double) b; -(Complex *) add: (Complex *) f; @end
В
Программа 9.1. Тестовая программа main.m // Совместно используемые имена методов: полиморфизм #import "Fraction.h #import "Complex.h" int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Fraction *f1 = [[Fraction alloc] init]; Fraction *f2 = [[Fraction alloc] init]; Fraction *fracResult; Complex *c1 = [[Complex alloc] init]; Complex *c2 = [[Complex alloc] init]; Complex *compResult; [f1 setTo: 1 over: 10]; [f2 setTo: 2 over: 15]; [c1 setReal: 18.0 andlmaginary: 2.5]; [c2 setReal: -5.0 andlmaginary: 3.2]; // добавление и вывод 2 комплексных чисел [d print]; NSLog (@" +"); [с2 print]; NSLog (@и............ "); compResult = [d add: с2]; [compResult print]; NSLog (@"\n"J; [c1 release]; [c2 release]; [compResult release]; // добавление и вывод 2 дробей [f1 print]; NSLog (@" +"); [f2 print]; NSLog (<§>"-—"); fracResult = [f1 add: f2]; [fracResult print]; [f1 release]; [f2 release]; [fracResult release]; [pool drain]; return 0; }
Вывод программы 9.1 18 + 2.5i + -5 + 3.2i 13 + 5.7i 1/10 + 2/15 7/30
Отметим, что оба класса, Fraction и Complex, содержат методы add: и print. Но откуда система знает, какие методы нужно вызывать при выполнении следующих выражений с сообщениями? compResult = [d add: с2]; [compResult print];
Системе выполнения (runtime) Objective-C известно, что с1, получатель первого сообщения, является объектом класса Complex. Поэтому выбирается метод add:, определенный для класса Complex. Система выполнения Objective-C определяет также, что compResult является объектом класса Complex, поэтому она выбирает метод print, определенный в классе Complex, чтобы вывести результат сложения. То же самое относится к следующим выражениям с сообщениями. fracResult = [f1 add: f2]; [fracResult print];
Примечание. Система всегда содержит информацию о классе, которому принадлежит объект. Это позволяет ей принимать нужные решения во время выполнения, не во время компиляции. Подробнее об этом рассказывается в главе 13. Выбор методов из класса Fraction выполняется при оценке выражения с сообщением в зависимости от класса П и fracResult.
Возможность использования одного имени из различных классов называется полиморфизмом. Полиморфизм позволяет вам разрабатывать набор классов с одинаковым именем метода. В определение каждого класса включается код, необходимый для ответа на вызов данного определенного метода, и это делает его независимым от определений в других классах. Это позволяет также добавлять новые классы, которые могут отвечать на вызов методов
Примечание. Именно классы Fraction и Complex (а не тестовая программа) должны предусматривать освобождение памяти, занимаемой результатами их методов add:. На самом деле эти объекты должны освобождаться автоматически (autorelease). Подробнее об этом рассказывается в главе 18. 9.2. Динамическое связывание и тип id
В главе 4 уже говорилось, что тип данных id является обобщенным типом объекта. Это означает, что id используется для хранения объектов, которые принадлежат любому классу. Он используется для хранения в переменной различных типов объектов. Рассмотрим программу 9.2 и ее вывод. // Пример динамического контроля типов и динамического связывания #import Traction.h" #import "Complex.h" int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; id dataValue; Fraction *f1 = [[Fraction alloc] init]; Complex *c1 = [[Complex alloc] init]; [f1 setTo: 2 over: 5]; [c1 setReal: 10.0 andlmaginary: 2.5]; // в первый раз dataValue присваивается дробь (fraction) dataValue = f1; [dataValue print]; // теперь dataValue присваивается комплексное число (complex) dataValue = c1; [dataValue print]; [c1 release]; [f1 release]; [pool drain]; return 0; }
Вывод программы 9.2 2/5 10 + 2.5i
Переменная dataValue объявляется как объект типа id, поэтому dataValue можно использовать для хранения в программе объекта любого типа. Отметим, что в строке объявления не используется «звездочка»: id dataValue;
Объекту f 1 типа Fraction присваивается дробь 2/5, переменной с1 типа Complex присваивается значение (10 + 2.5i). Оператор dataValue = f 1;
сохраняет f1 в dataValue. Вы можете вызвать с dataValue любой из методов, допустимых в объекте типа Fraction, хотя dataValue имеет тип id, а не Fraction. Но как система определяет, какой метод нужно вызвать, если в dataValue можно сохранять объект любого типа? Если система встречает выражение с сообщением [dataValue print];
откуда она знает, какой метод print нужно вызвать? Ведь методы print определены и в классе Fraction, и в классе Complex.
Как уже говорилось, система Objective-C всегда следит за классом, которому принадлежит объект. Это также определяется концепциями динамического контроля типов и динамического связывания: система принимает решение о классе объекта и о методе для его динамического вызова во время выполнения, а не во время компиляции.
Поэтому во время выполнения программы, прежде чем передать dataValue сообщение print, система проверяет класс объекта, хранящегося в dataValue. В случае программы 9.2 эта переменная содержит объект типа Fraction, поэтому используется метод print, определенный в классе Fraction.
Во втором случае а с1 типа Complex присваивается dataValue. Затем выполняется следующее выражение с сообщением: [dataValue print];
Поскольку теперь dataValue содержит объект, принадлежащий классу Complex, для выполнения выбирается метод print из этого класса.
Это простой пример, но вы можете распространить эту концепцию на более сложные приложения. В сочетании с концепцией полиморфизма динамическое связывание и динамический контроль типов позволяют легко писать коды, которые передают одинаковое сообщение объектам из различных классов.
Например, метод draw можно использовать для рисования графических объектов на экране. У вас могут быть различные методы draw, определенные для каждого из ваших графических объектов, таких как тексты, окружности, прямоугольники, окна и т.д. Если графический объект, который нужно нарисовать, сохраняется, например, в переменной типа id с именем currentObject, то его можно нарисовать на экране, просто передав ему сообщение draw: [currentObject draw];
Вы можете сначала проверить, отвечает ли на метод draw объект, хранящийся в currentObject. Ниже вы увидите, как это делать. 9.3. Проверка на этапе компиляции и проверка на этапе выполнения