Специфический параметр типа класса в 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
}