Кластеры класса

Кластеры класса являются шаблоном разработки, из которого платформа Основы делает широкое применение. Группа кластеров класса много частных бетонов разделяет на подклассы под общедоступным абстрактным суперклассом. Группировка классов таким образом упрощает публично видимую архитектуру объектно-ориентированной платформы, не сокращая ее функциональное богатство. Кластеры класса основываются на Абстрактном шаблоне разработки Фабрики.

Без кластеров класса: простое понятие, но сложный интерфейс

Для иллюстрирования кластерной архитектуры класса и ее преимуществ рассмотрите проблему построения иерархии классов, определяющей объекты сохранить числа различных типов (char, int, float, double). Поскольку числа различных типов имеют много функций вместе (они могут быть преобразованы от одного типа до другого и могут быть представлены как строки, например), они могли быть представлены единым классом. Однако их требования хранения отличаются, таким образом, это неэффективно для представления их всех тем же классом. Принимая этот факт во внимание, можно было разработать архитектуру класса, изображенную на рисунке 1-1 для решения проблемы.

Рисунок 1-1  простая иерархия для классов числа
A simple hierarchy for number classes

Number абстрактный суперкласс, объявляющий в его методах операции, характерные для его подклассов. Однако это не объявляет, что переменная экземпляра хранит число. Подклассы объявляют такие переменные экземпляра и долю в программируемом интерфейсе, объявленном Number.

До сих пор этот проект относительно прост. Однако, если обычно используемые модификации этих основных типов C приняты во внимание, схема иерархии классов больше походит на рисунок 1-2.

Рисунок 1-2  более полная иерархия классов числа
A more complete number class hierarchy

Простое понятие — создание класса для содержания числовых значений — может легко расцвести к более чем дюжине классов. Кластерная архитектура класса представляет проект, отражающий простоту понятия.

С кластерами класса: простое понятие и простой интерфейс

Применение образца кластерной структуры класса к этой проблеме приводит к иерархии классов на рисунке 1-3 (частные классы находятся в сером).

  Кластерная архитектура Класса рисунка 1-3 применилась к классам числа
Class cluster architecture applied to number classes

Пользователи этой иерархии видят только один общедоступный класс, 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.

Табличные 1-1  кластеры Класса и их общедоступные суперклассы

Кластер класса

Общедоступные суперклассы

NSData

NSData

NSMutableData

NSArray

NSArray

NSMutableArray

NSDictionary

NSDictionary

NSMutableDictionary

NSString

NSString

NSMutableString

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

Этот факторинг интерфейса кластера помогает сделать программируемый интерфейс объектно-ориентированной платформы более выразительным. Например, вообразите объект, представляющий книгу, объявляющую этот метод:

- (NSString *)title;

Объект книги мог возвратить свою собственную переменную экземпляра или создать новый строковый объект и возврат, что — это не имеет значения. Это ясно из этого объявления, что не может быть изменена возвращаемая строка. Любая попытка изменить возвращенный объект выявит предупреждение компилятора.

Создание подклассов в кластере класса

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

Если Вы находите, что кластер не обеспечивает функциональность Ваши потребности программы, то подкласс может быть в порядке. Например, предположите, что Вы хотите создать объект массива, хранение которого основано на файле, а не основано на памяти, как в NSArray кластер класса. Поскольку Вы изменяете механизм базовой системы хранения класса, необходимо было бы создать подкласс.

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

Таким образом, если необходимо управлять хранением объекта, создайте истинный подкласс. Иначе, создайте составной объект, тот, встраивающий стандартный объект платформы Основы в объект Вашего собственного проекта. Следующие разделы предоставляют более подробную информацию на этих двух подходах.

Истинный подкласс

Новый класс, который Вы создаете в кластере класса, должен:

  • Будьте подклассом абстрактного суперкласса кластера

  • Объявите его собственное хранение

  • Переопределите все методы инициализатора суперкласса

  • Переопределите примитивные методы суперкласса (описанный ниже)

Поскольку абстрактный суперкласс кластера является единственным публично видимым узлом в иерархии кластера, первая точка очевидна. Это подразумевает, что новый подкласс наследует интерфейс кластера, но никакие переменные экземпляра, потому что абстрактный суперкласс не объявляет ни один. Таким образом вторая точка: подкласс должен объявить любые переменные экземпляра, в которых он нуждается. Наконец, подкласс должен переопределить любой метод, который он наследовал, это непосредственно получает доступ к переменным экземпляра объекта. Такие методы вызывают примитивными методами.

Примитивные методы класса формируют основание для его интерфейса. Например, возьмите NSArray класс, объявляющий интерфейс к объектам, управляющим массивами объектов. В понятии массив хранит много элементов данных, каждый из которых доступен индексом. NSArray выражает это абстрактное понятие через его два примитивных метода, count и objectAtIndex:. С этими методами как основа могут быть реализованы другие методы — полученные методы —; Таблица 1-2 дает два примера полученных методов.

Таблица 1-2  Полученные методы и их возможные реализации

Полученный метод

Возможная реализация

lastObject

Найдите, что последний объект путем отправки массива возражает этому сообщению: [self objectAtIndex: ([self count] –1)].

containsObject:

Найдите, что объект путем повторной отправки массива возражает objectAtIndex: сообщение, каждый раз постепенно увеличивая индекс до всех объектов в массиве было протестировано.

Подразделение интерфейса между примитивными и полученными методами делает подклассы создания проще. Ваш подкласс должен переопределить наследованные примитивы, но сделавший так может быть уверено, что все полученные методы, которые он наследовал, будут работать должным образом.

Примитивно полученное различие применяется к интерфейсу полностью инициализированного объекта. Вопрос как 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 изображает эту архитектуру.

Рисунок 1-4  объект, встраивающий кластерный объект
Embedding a Cluster Object

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

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];
    }
}