Стиль Cocoa: использование полиморфизма в коллекциях

#objective-c #cocoa #coding-style #polymorphism

#objective-c #cocoa #стиль кодирования #полиморфизм

Вопрос:

У меня есть модель данных, которая включает в себя большой список (массив) разнородных элементов. Существует всего 2-3 разных типа элементов, каждый из которых наследуется от базового класса. Используя классические примеры, предположим, что базовым классом является Vehicle , а подклассами являются Car , Train и Plane .

У меня есть более крупная модель-владелец / контроллер, который хочет работать с этим упорядоченным списком транспортных средств, и хотя некоторые операции являются общими (и находятся в базовом классе и переопределяются в подклассах), многие операции специфичны только для одного из видов элементов.

В итоге у меня получается много кода, который выглядит следующим образом:

 for (Vehicle * vehicle in vehicles) {
    if (![vehicle isKindOfClass:[Car class]]) {
         continue;
    }
    Car * car = (Car *)vehicle;

    // Do stuff only with "car".
}
  

Итак, у меня есть много -isKindOfClass: везде и много приведений базового класса к подклассу. Все это, конечно, работает, но, похоже, достаточно «запаха кода», чтобы заставить меня думать, что может быть более элегантный способ либо написания этого кода, либо проектирования моей объектной модели.

Мысли? Спасибо.

Ответ №1:

Я предполагаю, что обычным полиморфным шаблоном было бы перенести тело цикла в рассматриваемые классы, чтобы ваш цикл превратился в

 for (Vehicle * vehicle in vehicles) {
    [vehicle doSubclassSpecificThing];
}
  

Тогда возникает вопрос, как совместно использовать общую часть тела цикла. Возможно, вы могли бы разбить его на серию фрагментов:

 for (Vehicle * vehicle in vehicles) {
    /* common code */
    [vehicle doThingy];
    /* further common code */
    [vehicle doOtherThingy];
}
  

Или вы могли бы использовать -doSubclassSpecificThing, чтобы сначала вызвать [super doSubclassSpecificThing] и поместить общую часть в базовый класс, если все это стоит на первом месте.

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

Комментарии:

1. Спасибо. Это полезно тем, что говорит мне, что я наполовину ошибаюсь в своем дизайне и наполовину в своем мышлении. Я думаю, более точно сказать, что объекты наполовину полиморфны. Существует множество циклов, которые должны работать с объектами. Многие из них являются просто «doSubclassSpecificThing», но некоторые невероятно специфичны (например, «setupAirbags»? на Car), которым просто не нужно касаться других типов объектов; у них нет аналога. Но все они находятся в одной коллекции, потому что они представляют собой упорядоченный инвентарь (растягивая метафору, но привет). Ваше обсуждение полезно для уточнения моего дизайна. Вернемся к доске для размышлений.

Ответ №2:

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

Тем не менее, в вашем примере этот конкретный цикл заинтересован в подмножестве элементов массива, которые принадлежат одному из подклассов, а именно Car . В этом случае вы можете использовать [-NSArray indexesOfObjectsPassingTest:] для создания набора индексов, содержащего только индексы Car объектов. Сделав это, вы можете повторить набор индексов, зная, что он указывает на элементы в исходном массиве, классом которых является Car . Например:

 NSIndexSet *cars = [vehicles indexesOfObjectsPassingTest:^(id obj, NSUInteger idx,   BOOL *stop) {
    return (BOOL)([obj isKindOfClass:[Car class]]);
}];

[cars enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
    Car *car = [vehicles objectAtIndex:idx];
    // Do stuff that’s specific to cars
}];
  

Комментарии:

1. Справедливое замечание: «полиморфизм» здесь вводит в заблуждение. Все объекты имеют общую заглушку данных / функциональности, но все расширяют эту базу до уникальных поведений. Предварительная фильтрация, безусловно, является более чистым способом написания циклов.

Ответ №3:

Я бы устранил приведения и изолировал проверки isKindOfClass набором методов, которые фильтруют коллекцию, чтобы в ней были только элементы нужного типа.

Заголовок:

 @interface VehicleManager : NSObject {
    @private
    NSArray *vehicles;
}

@property (readonly) NSArray *vehicles;
@property (readonly) NSArray *cars;
@property (readonly) NSArray *planes;
@property (readonly) NSArray *trains;
@end
  

Файл реализации:

 @implementation VehicleManager
@synthesize vehicles;

static NSArray *MyFilterArrayByClass(NSArray *array, Class class) {
    NSMutableArray *result = [NSMutableArray array];
    for (id object in array) {
        if ([object isKindOfClass:class]) {
            [result addObject:object];
        }
    }
    return resu<
}

- (NSArray *)cars {
    return MyFilterArrayByClass([self vehicles], [Car self]);
}

- (NSArray *)planes {
    return MyFilterArrayByClass([self vehicles], [Plane self]);
}

- (NSArray *)trains {
    return MyFilterArrayByClass([self vehicles], [Train self]);
}

- (BOOL)areAllCarsParked {
    BOOL allParked = YES;
    for (Car *car in [self cars]) {
        allParked = allParked amp;amp; [car isParked];
    }
    return allParked;
}

@end
  

Комментарии:

1. Спасибо, Джон. Это решение, наиболее близкое к моему варианту использования.