Стиль какао: использование полиморфизма в коллекциях - PullRequest
3 голосов
/ 22 апреля 2011

У меня есть модель данных, которая включает в себя большой список (массив) разнородных элементов. Существует всего 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: везде и много приведения базового класса к подклассу. Конечно, все это работает, но кажется, что «запаха кода» достаточно, чтобы заставить меня подумать, что может быть более элегантный способ либо написания этого кода, либо разработки моей объектной модели.

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

Ответы [ 3 ]

3 голосов
/ 22 апреля 2011

Полагаю, что обычным полиморфным шаблоном было бы вытолкнуть тело цикла в рассматриваемые классы, поэтому ваш цикл превращается в

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

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

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

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

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

2 голосов
/ 22 апреля 2011

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

Тем не менее, в вашем примере этот конкретный цикл заинтересован в подмножестве элементов массива, принадлежащих одному из подклассов, а именно 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 голос
/ 22 апреля 2011

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

@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 result;
}

- (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 && [car isParked];
    }
    return allParked;
}

@end
...