Специфический параметр типа класса в Objective-C
Я хочу принять объект класса в качестве параметра конструктору одного из моих классов. Он должен выполнять с ним много настраиваемой работы, и я хочу отвлечь это от пользователя этого класса.
Например, скажем, мой класс - Дилерский центр, и я хочу инициализировать его любым типом транспортного средства.
Итак, у меня есть следующая иерархия:
Dealership : NSObject
Vehicle : NSObject
Truck : Vehicle
Van : Vehicle
Car : Vehicle
Что я хочу сделать, внутри Dealership, реализовать следующий инициализатор:
- (id)initWithVehicle:(Class)aVehicle;
Вместо того, чтобы принимать общий класс, я хотел бы ограничить его только классами типа "Транспортное средство" (которое будет включать все мои унаследованные классы). Я мог бы просто проверить это свойство в инициализаторе, но было бы здорово, если бы был способ обеспечить это во время компиляции, а не ждать обратной связи во время работы.
Кажется, вы можете ссылаться на класс, чтобы ограничить классы, реализующие определенный интерфейс, поэтому я мог бы сделать там хакерство. Есть ли способ ссылаться на объекты класса определенного типа?
EDIT. Обратите внимание, что я редактировал примеры классов, потому что исходный пример немного вводил в заблуждение. Этот пример предназначен только для демонстрационных целей, а не для фактической иерархии классов, с которой я работаю.
Ответы
Ответ 1
Не во время компиляции, но вы можете освободить self
и вернуть nil
, если класс недействителен:
- (id)initWithCar: (Class)carClass {
self = [super init];
if (self) {
if (![carClass isSubclassOfClass:[Car class]]) {
[self release];
self = nil;
} else {
// Normal initialization here.
}
}
return self;
}
То, что вы ближе всего к тому, что вы захотите.
Но такой дизайн предполагает, что вам нужно переосмыслить свою иерархию классов. Вместо того, чтобы передавать подкласс Car
, вы должны иметь класс Manufacturer
. Что-то вроде этого:
@interface Manufacturer : NSObject
+ (id)manufacturerWithName: (NSString *)name;
- (NSArray *)allCars;
@property (readonly) Car *bestsellingCar;
// etc.
@end
#define kManufacturerVolvo [Manufacturer manufacturerWithName: @"Volvo"]
#define kManufacturerToyota [Manufacturer manufacturerWithName: @"Toyota"]
// etc.
И затем этот инициализатор для Дилерства:
- (id)initWithManufacturer: (Manufacturer *)aManufacturer;
Ответ 2
Вы можете обеспечить проверку протокола:
- (id)initWithVehicle:(id<VehicleProtocol>)aVehicle;
...
}
Объявление объекта как id сообщает компилятору, что вам все равно, какой тип объекта является объектом, но вам все равно, что он соответствует указанному протоколу VehicleProtocol **.
Ответ 3
Я не уверен, какое именно решение вы получаете, но, возможно, рассматривая Vehicle
как кластер классов, вы сможете объединить общие функции под одним зонтиком и иметь кластер классов для управления различными реализациями (и замаскировать их от пользователя)?
Например, NSString
делает это; базовые экземпляры обычно имеют тип NSCFString
, если только они не являются литералами (т.е. @"this is a literal, in code"
), и в этом случае они NSConstantString
s.
Здесь - выдержка из соответствующего сообщения в блоге, объясняющего кластеры классов
Вкратце, это дизайн, который позволяет вам включать в ваше приложение семейство функционально связанных объектов, сохраняя при этом код, взаимодействующий с этими объектами, слабо связанными, гибкими и простыми в обслуживании или обновлении.
Кластер классов соответствует этому набору общих объектов, которые ведут себя в соответствии с одним интерфейсом и, кроме того, направляет все их создание через этот интерфейс. Конструкция состоит из двух ключевых частей: а) один открытый абстрактный интерфейс, служащий "лицом" кластера, который рекламирует поддерживаемый API, и б) множество частных, конкретных подклассов этого интерфейса, ответственных за фактическое внедрение рекламируемого поведения суперкласса по-своему. В абстрактном суперклассе реализовано несколько методов, наиболее значимым из которых является метод Factory для экземпляров поставщика частных подклассов; другие общие функции, общие для всех подклассов, такие как аксессоры, также могут быть определены здесь и совместно использованы.
Пользователи кластера только видят один публичный суперкласс, не знают, что он фактически абстрактный, и ничего не знают о существовании каких-либо частных конкретных подклассов. Суперкласс предлагает метод создания Factory, который отвечает за определение того, какой подкласс подходит для любой заданной ситуации и прозрачно возвращает его экземпляр. Поскольку этот возвращенный объект реализует и ведет себя в соответствии с интерфейсом публичного суперкласса, пользователи могут просто предположить, что они получили прямой экземпляр этого суперкласса.
Ответ 4
Если вы хотите запустить дилерский центр с транспортным средством (я имею в виду инициализированный экземпляр), вы должны сделать:
- (id)initWithCar:(Vehicle *)carClass
{
//An extra check (only for DEBUG mode)
NSAssert([carClass isKindOfClass:[Vehicle class]]);
... //carClass is now a Vehicle instance
}