Способы замены массивного оператора if альтернативной конструкцией в Objective-C

#objective-c

#цель-c #objective-c

Вопрос:

У меня есть довольно длинный оператор if. Оператор if проверяет строку «type», чтобы определить, какой тип объекта следует создать. Вот пример…

 if ( [type rangeOfString:@"coin-large"].location != NSNotFound ) 
{
   ... create large coin ...
   mgr = gameLayer.coinLargeMgr;
}
else if ( [type rangeOfString:@"coin-small"].location != NSNotFound ) 
{
   mgr = gameLayer.coinLargeMgr;
}

... more else statements ...

myObject = [mgr getNewObject];
  

Инструкции «else-if» продолжаются для других типов объектов, которых сейчас около 20, и это число, вероятно, увеличится. Это работает довольно хорошо, но с точки зрения обслуживания и эффективности, я думаю, это можно было бы улучшить. Мой основной кандидат прямо сейчас — создать NSDictionary ключ для строки типа объекта (монета-маленькая, монета-большая и т.д.) И со значением объекта manager, Который должен быть привязан к этому типу. Идея заключается в том, что это был бы быстрый поиск типа объекта, который мне нужно создать. Не уверен, что это лучший подход, продолжаю рассматривать другие варианты, но мне любопытно, что люди здесь могли бы сделать для решения подобной проблемы. Любая помощь / обратная связь очень ценится.

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

1. Я бы поддержал ott. Выполните итерацию через NSArray или -Dictionary, заполненный NSStrings. Итак, вы делаете что-то вроде [type rangeOfString:[myArray objectAtIndex:i]] .location != NSNotFound . Кроме того, если вам нужны другие «менеджеры», установите ответственного менеджера в NSDictionary, чтобы его можно было легко найти.

2. Я подумал о чем-то более простом, например mgr = [dict valueForKey:coinString];

Ответ №1:

Вы можете использовать NSDictionary, заполненный «блоками» ObjC, для выполнения инструкции, подобной switch, которая выполняет желаемый код. Итак, создайте словарь с вашими строковыми ключами, сопоставленными с блоком кода, для выполнения при нахождении каждого из них:

 NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
                      ^{ NSLog(@"found key1"); }, @"key1", 
                      ^{ NSLog(@"found key2"); }, @"key2", 
                      nil];
  

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

Затем вместо вашего блока if / else вырежьте строковый ключ из любого получаемого вами ввода (или, возможно, вам не нужно будет его нарезать, что бы это ни было):

 NSString *input = ...
NSRange range = ...
NSString *key = [input substringWithRange:range]; 
  

И выполните (быстрый) поиск кода по словарю для выполнения. Затем выполните:

 void (^myBlock)(void) = [dict objectForKey:key];
myBlock();
  

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

1. Хороший подход с использованием блоков. Другой подход, который поддерживал бы логику создания экземпляра отдельно (что может быть или не быть желательным в зависимости от существующей архитектуры), заключался бы в сохранении соответствующего имени метода в виде строки в словаре. Затем вы можете динамически генерировать селектор из строки. Это менее эффективно, чем использование блоков или статических селекторов, но это возможно.

2. Убедитесь, что, если вы не используете ARC, вы вручную копируете / автоматически выпускаете блоки, поскольку блоки распределены в стеке.

3. Я поддерживаю комментарий Wevah выше.

4. На самом деле, вам нужно скопировать блок независимо от того, используете вы ARC или нет. ARC автоматически обрабатывает только возвращаемый регистр; в качестве аргумента он все равно должен быть скопирован.

5. Хотя я считаю, что в случае со «статическим инициализатором» (о котором я упоминал) блоки копировать не нужно.

Ответ №2:

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

 NSDictionary *stringsToManagers = 
    [NSDictionary dictionaryWithObjectsAndKeys:
         @"coin-large", gameLayer.coinLargeMgr,
         @"coin-small", gameLayer.coinSmallMgr,
         nil];

// this is assuming that type may contain multiple types; otherwise
// just use [stringsToManagers objectForKey:string]
for(NSString *string in [stringsToManagers allKeys])
{
    if([type rangeOfString:string].location != NSNotFound)
    {
         [[stringsToManagers objectForKey:string] addNewObject];
         // or get it and store it wherever it should go
    }
}
  

Если все, что делают менеджеры, это отправляют соответствующие объекты, более объектно-ориентированный подход может быть:

 NSDictionary *stringsToClasses = 
    [NSDictionary dictionaryWithObjectsAndKeys:
         @"coin-large", [LargeCoin class],
         @"coin-small", [SmallCoin class],
         nil];

// this is assuming that type may contain multiple types; otherwise
// just use [stringsToManagers objectForKey:string]
for(NSString *string in [stringsToManagers allKeys])
{
    if([type rangeOfString:string].location != NSNotFound)
    {
         id class = [stringsToManagers objectForKey:string];

         id newObject = [[class alloc] init];
         // this is exactly the same as if, for example, you'd
         // called [[LargeCoin alloc] init] after detecting coin-large
         // within the input string; you should obviously do something
         // with newObject now
    }
}
  

Это может избавить вас от необходимости писать какие-либо менеджеры, если структура вашей программы в остальном подходит.

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

1. Спасибо за отзыв, Томми. Я собираюсь придерживаться этого подхода.