Программирование на Objective-C 2.0
Шрифт:
Мы определили класс Square как подкласс класса Rectangle, который объявлен в заголовке файла Rectangle.h. Здесь не требуется добавлять какие-либо переменные экземпляра, но добавлены новые методы setSide: и side.
Для квадрата достаточно задать только одну сторону, которая представляется как два числа. Все это скрыто от пользователя класса Square. Пользователю не нужно думать об этих деталях, поскольку здесь действует инкапсуляция, о которой мы говорили раньше.
В методе setSide: используется метод, наследуемый из класса Rectangle, задающий ширину и высоту прямоугольника. Таким образом, setSide: вызывает метод setWidth:andHeight: из класса Rectangle, передавая параметр s как значение для ширины (width) и высоты (height).Больше ничего не требуется. Тот, кто будет работать с объектом класса Square, может задавать размеры квадрата с помощью
Вывод программы 8.3 Square (квадрат) s = 5 Area (площадь) = 25, Perimeter (периметр) = 20
Способ, с помощью которого был определен класс Square, является базовым методом работы с классами в Objective-C: расширение того, что уже кем-то сделано, чтобы реализовать то, что вам нужно. Помимо этого, существует механизм категорий (category), который позволяет добавлять новые методы к существующему определению класса по модульному принципу, то есть без необходимости постоянно добавлять новые определения в файлы секций interface и implementation. Это особенно удобно в тех случаях, когда вы не имеете доступа к исходному коду. Описание категорий см. в главе 11. Класс для точки и выделение памяти
Класс Rectangle используется только для хранения размеров прямоугольника. В реальных графических приложениях требуется следить за всевозможной дополнительной информацией, например, цветом заполнения прямоугольника, цветом линий, точкой начала координат прямоугольника (origin) внутри окна и т.д. Для этого можно легко расширить существующий класс. Сейчас мы реализуем идею начала координат прямоугольника. Примем за начало координат декартовы координаты (х, у) левого нижнего угла прямоугольника. Если вы разрабатываете чертежное приложение, эта точка может представлять местоположение прямоугольника внутри окна (рис. 8.4).
Рис. 8.4. Прямоугольник, нарисованный в окне На рис. 8.4 точка начала координат прямоугольника представлена как (xl,y 1). Вы можете расширить класс Rectangle, чтобы сохранять координаты х,у точки начала прямоугольника в виде двух отдельных значений или определить класс с именем XYPoint (возможно, вы помните об этой задаче из упражнения 7 главы 3). #import <Foundation/Foundation.h> @interface XYPoint: NSObject { int x; int y; } @property int x, y; (void) setX: (int) xVal andY: (int) yVal; @end
Теперь вернемся к классу Rectangle. Нам нужно сохранять координаты начала прямоугольника, поэтому требуется добавить к определению этого класса еще одну переменную экземпляра с именем origin. @interface Rectangle: NSObject { int width; int height; XYPoint *origin; }
Вполне разумно добавить метод, с помощью которого задаются и считываются координаты начала прямоугольника. Мы не будем здесь синтезировать методы доступа (accessor methods) для координат начала, а напишем их сами. Директива @class
На данный момент вы можете работать с прямоугольниками (и квадратами), задавая их ширину, высоту и координаты начала. Рассмотрим в полном виде файл Rectangle.h секции interface. #import <Foundation/Foundation.h> @class XYPoint; @interface Rectangle: NSObject { int width; int height; XYPoint *origin; @property int width, height; -(XYPoint *) origin; -(void) setOrigin: (XYPoint *) pt; -(void) setWidth: (int) w andHeight: (int) h -(int) area; -(int) perimeter; @end
В файле Rectangle.h использована новая директива: @class XYPoint;
Она требуется нам, поскольку компилятору нужно знать, что представляет собой XYPoint, когда он встречается в одной из переменных экземпляра, определенных для Rectangle. Имя этого класса используется также в объявлениях типов аргумента и возвращаемого значения для наших методов setOrigin: и origin соответственно. У вас есть и другой вариант выбора — импортировать файл заголовка, например, в следующем виде: #import "XYPoint.h"
Директива @class
эффективнее, поскольку компилятору не нужно обрабатывать весь файл XYPoint.h (хотя это небольшой файл); компилятору достаточно знать, что XYPoint является именем класса. Если нужна ссылка на один из методов класса XYPoint, то директива @class недостаточна, поскольку компилятору потребуется дополнительная информация: аргументы, передаваемые методу, их типы, тип возвращаемого значения метода.Заполним формы для нового класса XYPoint и новых методов класса Rectangle, чтобы протестировать их в программе. В программе 8.4 имеется файл секции implementation для класса XYPoint.
Сначала в ней показаны новые методы для класса Rectangle. #import "XYPoint.h" -(void) setOrigin: (XYPoint *) pt { origin = pt; -(XYPoint *) origin { return origin; ) @end
Затем показаны полные определения классов XYPoint и Rectangle и тестовая программа для их проверки. #import <Foundation/Foundation.h> @interface XYPoint: NSObject { int x; int y; } @property int х, у; -(void) setX: (int) xVal andY: (int) yVal; @end #import "XYPoint.h" @implementation XYPoint @synthesize x, y; -(void) setX: (int) xVal andY: (int) yVal { x = xVal; у = yVal; } @end #import <Foundation/Foundation.h> @class XYPoint; @interface Rectangle: NSObject { int width; int height; XYPoint *origin; } @property int width, height; -(XYPoint *) origin; -(void) setOrigin: (XYPoint *) pt; -(void) setWidth: (int) w andHeight: (int) h; -(int) area; -(int) perimeter; @end #import "Rectangle.h" @implementation Rectangle @synthesize width, height; -(void) setWidth: (int) w andHeight: (int) h width = w; height = h; -(void) setOrigin: (XYPoint *) pt origin = pt; -(int) area { return width * height; } -(int) perimeter { return (width + height) * 2; } -(Point *) origin { return origin; } @end #import "Rectangle.h" #import "XYPoint.h" int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Rectangle *myRect = [[Rectangle alloc] init]; XYPoint * myPoint = [[XYPoint alloc] init]; [myPoint setX: 100 andY: 200]; [myRect setWidth: 5 andHeight: 8]; myRect.origin = myPoint; NSLog (@"Rectangle w = %i, h = %i", myRect.width, myRect.height); NSLog (@"Origin at (%i, %i)"> myRect.origin.x, myRect.origin.y); NSLog (@»Area = %i, Perimeter = %i», [myRect area], [myRect perimeter]); [myRect release]; [myPoint release]; [pool drain]; return 0; }
Вывод программы 8.4 Rectangle (Прямоугольник) w = 5, h = 8 Origin at (Координаты начала) (100, 200) Area (Площадь) = 40, Perimeter (Периметр) = 26
Внутри процедуры main выделена память и инициализирован объект класса Rectangle с именем myRect и объект класса XYPoint с именем myPoint. С помощью метода setX:andY: объекту myPoint присваивается значение (100, 200). После задания ширины и высоты этого прямоугольника (5 и 8 соответственно) вызывается метод setOrigin, чтобы задать для координат начала прямоугольника точку, указанную в myPoint. Затем с помощью трех вызовов процедуры NSLog выполняется считывание и вывод этих значений. В выражении myRect.origin.x
считывается объект класса XYPoint, возвращенный методом доступа origin, и применяется оператор «точка» для получения координаты «х» начала прямоугольника. В следующем выражении считывается координата «у» начала прямоугольника: myRect.origin.y Классы, владеющие своими объектами
Можете ли вы объяснить результаты вывода программы 8.5? #import "Rectangle.h" #import "XYPoint.h" int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Rectangle *myRect = [[Rectangle alloc] init]; XYPoint *myPoint = [[XYPoint alloc] init]; [myPoint setX: 100 andY: 200]; [myRect setWidth: 5 and Height: 8]; myRect.origin = myPoint; NSLog (@"Origin at (%i, %i)", myRect.origin.x, myRect.origin.y); [myPoint setX: 50 andY: 50]; NSLog (@"Origin at (%i, %i)", myRect.origin.x, myRect.origin.y); [myRect release]; [myPoint release]; [pool drain]; return 0; }
Вывод программы 8.5 Origin at (Координаты начала) (100, 200) Origin at (50, 50)
В этой программе значение объекта myPoint было изменено с (100, 200) на (50, 50), то есть были изменены координаты начала прямоугольника. Но почему это произошло? Здесь не было явным образом задано новое значение начала прямоугольника, почему оно изменилось? Вернемся к определению метода setOrigin:, чтобы понять причину: -(void) setOrigin: (XYPoint *) pt { origin = pt; }
При вызове метода setOrigin: с помощью выражения myRect.origin = myPoint;