Кластеры класса
Кластеры класса являются шаблоном разработки, из которого платформа Основы делает широкое применение. Группа кластеров класса много частных бетонов разделяет на подклассы под общедоступным абстрактным суперклассом. Группировка классов таким образом упрощает публично видимую архитектуру объектно-ориентированной платформы, не сокращая ее функциональное богатство. Кластеры класса основываются на Абстрактном шаблоне разработки Фабрики.
Без кластеров класса: простое понятие, но сложный интерфейс
Для иллюстрирования кластерной архитектуры класса и ее преимуществ рассмотрите проблему построения иерархии классов, определяющей объекты сохранить числа различных типов (char
, int
, float
, double
). Поскольку числа различных типов имеют много функций вместе (они могут быть преобразованы от одного типа до другого и могут быть представлены как строки, например), они могли быть представлены единым классом. Однако их требования хранения отличаются, таким образом, это неэффективно для представления их всех тем же классом. Принимая этот факт во внимание, можно было разработать архитектуру класса, изображенную на рисунке 1-1 для решения проблемы.
Number
абстрактный суперкласс, объявляющий в его методах операции, характерные для его подклассов. Однако это не объявляет, что переменная экземпляра хранит число. Подклассы объявляют такие переменные экземпляра и долю в программируемом интерфейсе, объявленном Number
.
До сих пор этот проект относительно прост. Однако, если обычно используемые модификации этих основных типов C приняты во внимание, схема иерархии классов больше походит на рисунок 1-2.
Простое понятие — создание класса для содержания числовых значений — может легко расцвести к более чем дюжине классов. Кластерная архитектура класса представляет проект, отражающий простоту понятия.
С кластерами класса: простое понятие и простой интерфейс
Применение образца кластерной структуры класса к этой проблеме приводит к иерархии классов на рисунке 1-3 (частные классы находятся в сером).
Пользователи этой иерархии видят только один общедоступный класс, Number
, таким образом, как возможно выделить экземпляры надлежащего подкласса? Ответ находится в способе, которым абстрактный суперкласс обрабатывает инстанцирование.
Создание экземпляров
Абстрактный суперкласс в кластере класса должен объявить методы для создания экземпляров его частных подклассов. Это - ответственность суперкласса распределить объект надлежащего подкласса на основе метода создания, который Вы вызываете — Вы не делаете, и не может, выбрать класс экземпляра.
В платформе Основы Вы обычно создаете объект вызовом a +
имя класса...
метод или alloc...
и init...
методы. Взятие платформы Основы NSNumber
класс как пример, Вы могли отправить эти сообщения для создания объектов числа:
NSNumber *aChar = [NSNumber numberWithChar:’a’]; |
NSNumber *anInt = [NSNumber numberWithInt:1]; |
NSNumber *aFloat = [NSNumber numberWithFloat:1.0]; |
NSNumber *aDouble = [NSNumber numberWithDouble:1.0]; |
Вы не ответственны за выпуск объектов, возвращенных из методов фабрики. Много классов также обеспечивают стандарт alloc...
и init...
методы для создания объектов, требующих, чтобы Вы управляли их освобождением.
Каждый объект возвратился —aChar
, anInt
, aFloat
, и aDouble
— может принадлежать различному частному подклассу (и фактически делает). Несмотря на то, что членство в классе каждого объекта скрыто, его интерфейс общедоступен, будучи интерфейсом, объявленным абстрактным суперклассом, NSNumber
. Несмотря на то, что это не точно корректно, удобно рассмотреть aChar
, anInt
, aFloat
, и aDouble
объекты быть экземплярами NSNumber
класс, потому что они создаются NSNumber
методы класса и получили доступ через методы экземпляра, объявленные NSNumber
.
Кластеры класса с многократными общедоступными суперклассами
В примере выше, один абстрактный общедоступный класс объявляет интерфейс для многократных частных подклассов. Это - кластер класса в самом чистом смысле. Это также возможно, и часто желательно, для имения два (или возможно больше) абстрактные общедоступные классы, объявляющие интерфейс для кластера. Это очевидно в платформе Основы, включающей кластеры, перечисленные в Таблицу 1-1.
Кластер класса | Общедоступные суперклассы |
---|---|
| |
| |
| |
| |
Другие кластеры этого типа также существуют, но они ясно иллюстрируют, как два абстрактных узла сотрудничают при объявлении программируемого интерфейса к кластеру класса. В каждом из этих кластеров один общедоступный узел объявляет методы, что все кластерные объекты могут ответить на, и другой узел объявляет методы, которые являются только подходящими для кластерных объектов, позволяющих их содержанию быть измененным.
Этот факторинг интерфейса кластера помогает сделать программируемый интерфейс объектно-ориентированной платформы более выразительным. Например, вообразите объект, представляющий книгу, объявляющую этот метод:
- (NSString *)title; |
Объект книги мог возвратить свою собственную переменную экземпляра или создать новый строковый объект и возврат, что — это не имеет значения. Это ясно из этого объявления, что не может быть изменена возвращаемая строка. Любая попытка изменить возвращенный объект выявит предупреждение компилятора.
Создание подклассов в кластере класса
Кластерная архитектура класса включает компромисс между простотой и расширяемостью: Наличие нескольких общедоступных классов помогает для множества частных, упрощает изучать и использовать классы в платформе, но несколько тяжелее создавать подклассы в любом из кластеров. Однако, если редко необходимо создать подкласс, тогда кластерная архитектура ясно выгодна. Кластеры используются в платформе Основы в просто этих ситуациях.
Если Вы находите, что кластер не обеспечивает функциональность Ваши потребности программы, то подкласс может быть в порядке. Например, предположите, что Вы хотите создать объект массива, хранение которого основано на файле, а не основано на памяти, как в NSArray
кластер класса. Поскольку Вы изменяете механизм базовой системы хранения класса, необходимо было бы создать подкласс.
С другой стороны, в некоторых случаях это могло бы быть достаточным (и проще) определить класс, встраивающий в нем объект от кластера. Скажем, то, что Ваша программа должна быть предупреждена каждый раз, когда изменяются некоторые данные. В этом случае создание простого класса, обертывающего объект данных, который определяет платформа Основы, может быть лучшим подходом. Объект этого класса мог вмешаться в сообщения, изменяющие данные, прерывая сообщения, действуя на них, и затем передавая им встроенному объекту данных.
Таким образом, если необходимо управлять хранением объекта, создайте истинный подкласс. Иначе, создайте составной объект, тот, встраивающий стандартный объект платформы Основы в объект Вашего собственного проекта. Следующие разделы предоставляют более подробную информацию на этих двух подходах.
Истинный подкласс
Новый класс, который Вы создаете в кластере класса, должен:
Будьте подклассом абстрактного суперкласса кластера
Объявите его собственное хранение
Переопределите все методы инициализатора суперкласса
Переопределите примитивные методы суперкласса (описанный ниже)
Поскольку абстрактный суперкласс кластера является единственным публично видимым узлом в иерархии кластера, первая точка очевидна. Это подразумевает, что новый подкласс наследует интерфейс кластера, но никакие переменные экземпляра, потому что абстрактный суперкласс не объявляет ни один. Таким образом вторая точка: подкласс должен объявить любые переменные экземпляра, в которых он нуждается. Наконец, подкласс должен переопределить любой метод, который он наследовал, это непосредственно получает доступ к переменным экземпляра объекта. Такие методы вызывают примитивными методами.
Примитивные методы класса формируют основание для его интерфейса. Например, возьмите NSArray
класс, объявляющий интерфейс к объектам, управляющим массивами объектов. В понятии массив хранит много элементов данных, каждый из которых доступен индексом. NSArray
выражает это абстрактное понятие через его два примитивных метода, count
и objectAtIndex:
. С этими методами как основа могут быть реализованы другие методы — полученные методы —; Таблица 1-2 дает два примера полученных методов.
Полученный метод | Возможная реализация |
---|---|
| Найдите, что последний объект путем отправки массива возражает этому сообщению: |
| Найдите, что объект путем повторной отправки массива возражает |
Подразделение интерфейса между примитивными и полученными методами делает подклассы создания проще. Ваш подкласс должен переопределить наследованные примитивы, но сделавший так может быть уверено, что все полученные методы, которые он наследовал, будут работать должным образом.
Примитивно полученное различие применяется к интерфейсу полностью инициализированного объекта. Вопрос как init...
методы должны быть обработаны в подклассе, также должен адресоваться.
В целом абстрактный суперкласс кластера объявляет много init...
и + className
методы. Как описано в Создании Экземпляров, абстрактный класс решает, какой бетон разделяют на подклассы для инстанцирования базируемый выбор init...
или + className
метод. Можно полагать, что абстрактный класс объявляет эти методы для удобства подкласса. Так как абстрактный класс не имеет никаких переменных экземпляра, он не имеет никакой потребности методов инициализации.
Ваш подкласс должен объявить свое собственное init...
(если это должно инициализировать свои переменные экземпляра), и возможно + className
методы. Это не должно полагаться ни на один из тех, которые это наследовало. Для поддержания его ссылки в цепочке инициализации это должно вызвать определяемый инициализатор своего суперкласса в его собственном определяемом методе инициализатора. Это должно также переопределить все другие наследованные методы инициализатора и реализовать их для поведения разумным способом. (См. Многократные Инициализаторы и Определяемый Инициализатор для обсуждения определяемых инициализаторов.) В кластере класса определяемый инициализатор абстрактного суперкласса всегда init
.
Истинные подклассы: пример
Скажем, то, что Вы хотите создать подкласс NSArray
, именованный MonthArray
, это возвращает имя месяца, данного его индексную позицию. Однако a MonthArray
объект фактически не сохранит массив имен месяца как переменная экземпляра. Вместо этого метод, возвращающий имя, данное индексную позицию (objectAtIndex:
) возвратит постоянные строки. Таким образом только двенадцать строковых объектов будут выделены, независимо от того сколько MonthArray
объекты существуют в приложении.
MonthArray
класс объявляется как:
#import <foundation/foundation.h> |
@interface MonthArray : NSArray |
{ |
} |
+ monthArray; |
- (unsigned)count; |
- (id)objectAtIndex:(unsigned)index; |
@end |
Обратите внимание на то, что MonthArray
класс не объявляет init...
метод, потому что это не имеет никаких переменных экземпляра для инициализации. count
и objectAtIndex:
методы просто покрывают наследованные примитивные методы, как описано выше.
Реализация MonthArray
класс похож на это:
#import "MonthArray.h" |
@implementation MonthArray |
static MonthArray *sharedMonthArray = nil; |
static NSString *months[] = { @"January", @"February", @"March", |
@"April", @"May", @"June", @"July", @"August", @"September", |
@"October", @"November", @"December" }; |
+ monthArray |
{ |
if (!sharedMonthArray) { |
sharedMonthArray = [[MonthArray alloc] init]; |
} |
return sharedMonthArray; |
} |
- (unsigned)count |
{ |
return 12; |
} |
- objectAtIndex:(unsigned)index |
{ |
if (index >= [self count]) |
[NSException raise:NSRangeException format:@"***%s: index |
(%d) beyond bounds (%d)", sel_getName(_cmd), index, |
[self count] - 1]; |
else |
return months[index]; |
} |
@end |
Поскольку MonthArray
переопределяет наследованные примитивные методы, полученные методы, которые это наследовало, будут работать должным образом без того, чтобы быть переопределенным. NSArray
lastObject
, containsObject:
, sortedArrayUsingSelector:
, objectEnumerator
, и другие методы работают без проблем на MonthArray
объекты.
Составной объект
Путем встраивания частного кластерного объекта в объект собственного проекта Вы создаете составной объект. Этот составной объект может полагаться на кластерный объект для своей основной функциональности, только прерывая сообщения, что составной объект хочет обработать некоторым определенным способом. Эта архитектура сокращает объем кода, который Вы должны записать и позволяете Вам использовать в своих интересах протестированный код, предоставленный Платформой Основы. Рисунок 1-4 изображает эту архитектуру.
Составной объект должен объявить, что себя подкласс абстрактного суперкласса кластера. Как подкласс, это должно переопределить примитивные методы суперкласса. Это может также переопределить полученные методы, но это не необходимо, потому что полученные методы работают через примитивные.
count
метод NSArray
класс является примером; реализация прошедшего объекта метода, который это переопределяет, может быть столь же простой как:
- (unsigned)count { |
return [embeddedObject count]; |
} |
Однако Ваш объект мог поместить код в своих собственных целях в реализации любого метода, который это переопределяет.
Составной объект: пример
Для иллюстрирования использования составного объекта предположите желание непостоянного объекта массива, что тесты изменяются против некоторых критериев проверки прежде, чем позволить любую модификацию содержанию массива. Следующий пример описывает вызванный класс ValidatingArray
, который содержит стандартный непостоянный объект массива. ValidatingArray
переопределения все примитивные методы, объявленные в его суперклассах, NSArray
и NSMutableArray
. Это также объявляет array
, validatingArray
, и init
методы, которые могут использоваться, чтобы создать и инициализировать экземпляр:
#import <foundation/foundation.h> |
@interface ValidatingArray : NSMutableArray |
{ |
NSMutableArray *embeddedArray; |
} |
+ validatingArray; |
- init; |
- (unsigned)count; |
- objectAtIndex:(unsigned)index; |
- (void)addObject:object; |
- (void)replaceObjectAtIndex:(unsigned)index withObject:object; |
- (void)removeLastObject; |
- (void)insertObject:object atIndex:(unsigned)index; |
- (void)removeObjectAtIndex:(unsigned)index; |
@end |
Файл реализации показывает как, в init
метод ValidatingArray
класс, внедренный объект создается и присваивается embeddedArray переменной. Сообщения, которые просто получают доступ массив, но не изменять его содержание, передаются к внедренному объекту. Сообщения, которые могли изменить содержание, тщательно исследуются (здесь в псевдокоде) и передаются, только если они проходят гипотетический тест проверки.
#import "ValidatingArray.h" |
@implementation ValidatingArray |
- init |
{ |
self = [super init]; |
if (self) { |
embeddedArray = [[NSMutableArray allocWithZone:[self zone]] init]; |
} |
return self; |
} |
+ validatingArray |
{ |
return [[[self alloc] init] autorelease]; |
} |
- (unsigned)count |
{ |
return [embeddedArray count]; |
} |
- objectAtIndex:(unsigned)index |
{ |
return [embeddedArray objectAtIndex:index]; |
} |
- (void)addObject:object |
{ |
if (/* modification is valid */) { |
[embeddedArray addObject:object]; |
} |
} |
- (void)replaceObjectAtIndex:(unsigned)index withObject:object; |
{ |
if (/* modification is valid */) { |
[embeddedArray replaceObjectAtIndex:index withObject:object]; |
} |
} |
- (void)removeLastObject; |
{ |
if (/* modification is valid */) { |
[embeddedArray removeLastObject]; |
} |
} |
- (void)insertObject:object atIndex:(unsigned)index; |
{ |
if (/* modification is valid */) { |
[embeddedArray insertObject:object atIndex:index]; |
} |
} |
- (void)removeObjectAtIndex:(unsigned)index; |
{ |
if (/* modification is valid */) { |
[embeddedArray removeObjectAtIndex:index]; |
} |
} |