Чтение онлайн

ЖАНРЫ

Программирование на Objective-C 2.0
Шрифт:

Примечание. Это теория. Компилятор позволяет вам указать, что вы подчиняетесь протоколу, и выдает предупреждающие сообщения, только если вы не реализуете эти методы.

Отметим, что в протоколе нет ссылки ни на какие классы; это «бесклассовое» средство. Протоколу Drawing может подчиняться любой класс, не только подклассы GraphicObject.

Чтобы проверить, подчиняется ли объект какому-либо протоколу, можно использовать метод conformsToProtocol:. Например, вы хотите определить, подчиняется ли объект с именем currentObject протоколу Drawing, чтобы передавать этому объекту сообщения для рисования. Для этого можно написать следующее. id currentObject; ... if ([currentObject conformsToProtocol: @protocol (Drawing)] == YES) { // Передача сообщений currentObject paint, erase и/или outline ... }

Специальная

директива @protocol, которая используется здесь, принимает имя протокола и создает объект типа Protocol, который используется как аргумент методом conformsToProtocol:.

Вы можете воспользоваться помощью компилятора, чтобы проверить согласование с вашими переменными, заключив имя протокола в угловые скобки после имени типа: id <Drawing> currentObject;

Это указывает компилятору, что cunentObject будет содержать объекты, подчиняющиеся протоколу Drawing. Если присвоить currentObject объект статического типа, который не согласуется с протоколом Drawing (например, у вас есть несогласуюш ийся класс Square), то компилятор выдаст предупреждающее сообщение: warning: class 'Square' does not implement the 'Drawing' protocol (предупреждение: класс 'Square' не реализует протокол 'Drawing')

Здесь проверку выполняет компилятор, поэтому присваивание currentObject переменной типа id не приведет к выводу этого сообщения. Ддля объекта, хранящегося в переменной типа id, компилятор не сможет определить, подчиняется ли он протоколу Drawing.

В списке можно указать более одного протокола, если переменная будет содержать объект, подчиняющийся нескольким протоколам: id <NSCopying, NSCoding> myDocument;

Определение протокола можно расширить. В следующем объявлении протокола указывается, что протокол Drawing3D принимает также протокол Drawing. @protocol Drawing3D <Drawing>

Таким образом, класс, который принимает протокол Drawing3D, должен реализовать методы, перечисленные для этого протокола, а также методы из протокола Drawing.

И, наконец, категория тоже может принимать протокол, например: @interface Fraction (Stuff) <NSCopying, NSCoding>

Здесь Fraction содержит категорию Stuff, которая принимает протоколы NSCopying и NSCoding.

Как и имена классов, имена протоколов должны быть уникальными. Неформальные протоколы

В литературе встречается понятие неформального, свободного (informal) протокола. На самом деле это категория, содержащая список группы методов, но не реализующая их. Все (или почти все) наследуется из одного корневого объекта, поэтому неформальные категории часто определяются для корневого класса. Иногда неформальные протоколы называют также абстрактными (abstract) протоколами.

В header-файле могут встретиться объявления методов, которые выглядят следующим образом: @interface NSObject (NSComparisonMethods) -(BOOL)isEqualTo:(id)object; -(BOOL)isLessThanOrEqualTo:(id)object; -{B00L}isLessThan:(id)object; -<BOOL}isGreaterThanOrEqualTo:(id)object; BOOLJisGreaterThan: (id)object; -(BOOL)isNotEqualTo:(id)object; -{BOOL)doesContain:(id)object; -(B00L)isLike:(NSString *)object; -(BOOL)isCaselnsensitiveLike:(NSString *)object; @end

Здесь определяется категория с именем NSComparisonMethods для класса NSObject. В этом неформальном протоколе содержится список группы методов (в данном случае — девять), которые могут быть реализованы как часть этого протокола. Неформальный протокол — это фактически просто группа методов под определенным именем. Это полезно с точки зрения документирования и модульной организации методов.

Класс, где объявляется неформальный протокол, не реализует методы в самом этом классе. В подклассе, выбранном для реализации этих методов, требуется переобъявить их в секции interface, а также реализовать один или несколько из этих методов. В отличие от формальных протоколов, компилятор не оказывает никакой помощи с неформальными протоколами: здесь нет никакой концепции подчинения или проверки компилятором.

Если объект принимает какой-либо формальный протокол, этот объект должен подчиняться всем требуемым сообщениям в этом протоколе. Это можно сделать как на этапе выполнения (runtime), так и во время компиляции. Если объект принимает неформальный протокол, то он не обязан принять

все методы данного протокола (в зависимости от самого протокола). Подчинение неформальному протоколу можно сделать обязательным на этапе выполнения (с помощью respondsToSelector:), но не во время компиляции.

Примечание. Описанную выше директиву @optional (она была добавлена в Objective-C 2.0) можно использовать вместо неформальных протоколов. Она используется в нескольких классах UIKit (UIK.it — составная часть структур Cocoa Touch framework). 11.3. Составные объекты

Вы уже знаете несколько способов, позволяющих расширить определение класса с помощью таких средств, как подклассы и категории. Еще один способ — это определение класса, который содержит один или несколько объектов из других классов. Объект из этого класса называется составным (composite) объектом, поскольку он составлен из других объектов.

В качестве примера рассмотрим класс Square (квадрат), который мы определили в главе 8. Он был определен как подкласс класса Rectangle (прямоугольник), поскольку квадрат это прямоугольник с равными сторонами. Подкласс, который мы определяем наследует все переменные экземпляра и методы родительского класса. В некоторых случаях это нежелательно. Метод setWidth:andHeight: (задание ширины и высоты) класса Rectangle наследуется классом Square, но реально не относится к квадрату (хотя действует правильно). Кроме того, создавая подкласс, мы должны обеспечить правильность работы наследуемых методов, поскольку пользователи этого подкласса будут иметь к ним доступ.

Вместо подкласса можно определить новый класс, который содержит в качестве одной из своих переменных экземпляра объект из класса, который вы хотите расширить. Затем нужно определить в новом классе только те методы, которые для него подходят. В примере с классом Square можно определить Square следующим образом. @interface Square: NSObject { Rectangle *rect; } -(int) setSide: (int) s; -(int) side; (сторона) •(int) area; (площадь) -(int) perimeter; (периметр) @end

Здесь определен класс Square с четырьмя методами. В отличие от версии с подклассом, которая дает непосредственный доступ к методам класса Rectangle (setWidth:, setHeight:, setWidthiandHeight:, width и height), этих методов нет в определении для Square. Это имеет смысл, поскольку не все методы подходят для работы с квадратами.

Если мы определяем класс Square таким способом, то он становится ответственным за выделение памяти для прямоугольника (rectangle), который содержит. Например, без заметающих методов оператор Square *mySquare = [[Square alloc] init];

выделяет память для нового объекта типа Square, но не выделяет память для объекта типа Rectangle, хранящегося в его переменной экземпляра red.

Чтобы выполнить выделение памяти, требуется замещение метода init или добавление нового метода, например, initWithSide:. Этот метод может выделять память для переменной Rectangle red и задавать соответствующим образом сторону (side). Необходимо также заместить метод dealloc (как описано для класса Rectangle в главе 8), чтобы освободить память, используемую для Rectangle red, когда освобождается сам объект Square.

Определяя свои методы в классе Square, мы по-прежнему можем использовать методы класса Rectangle. Например, мы можем реализовать метод area следующим образом: -(int) area { return [rect area]; }

Реализацию остальных методов вы можете написать в качестве упражнения (см. ниже упражнение 5). Упражнения

Выполните расширение категории MathOps из программы 11.1, чтобы дополнительно включить метод invert, который возвращает дробь (Fraction), обратную получателю.

Добавьте в класс Fraction категорию с именем Comparison. В этой категории добавьте два метода в соответствии со следующими объявлениями.
– (BOOL) isEqualTo: (Fraction *) 1; -(int) compare: (Fraction *) f; Первый метод должен возвращать значение YES, если две дроби равны, и значение N0 в противном случае. Правильно сравнивайте дроби (например, при сравнении дробей 3/4 и 6/8 следует возвращать значение YES). Второй метод должен возвращать значение -1, если получатель меньше дроби, представляемой аргументом; возвращать 0, если две дроби равны; возвращать 1, если получатель больше дроби, представляемой аргументом.

Поделиться с друзьями: