trabajo pensamientos negativos mental mensajes memoria mejorar frases ejemplos cortos concentracion como atencion objective-c design-patterns switch-statement

objective c - pensamientos - Objective-C cambiar usando objetos?



pensamientos negativos ejemplos (14)

Estoy haciendo una programación de Objective-C que implica analizar un NSXmlDocument y rellenar las propiedades de un objeto a partir del resultado.

La primera versión se veía así:

if([elementName compare:@"companyName"] == 0) [character setCorporationName:currentElementText]; else if([elementName compare:@"corporationID"] == 0) [character setCorporationID:currentElementText]; else if([elementName compare:@"name"] == 0) ...

Pero no me gusta el patrón if-else-if-else que esto produce. Al observar la declaración del switch , veo que solo puedo manejar ints , chars , etc. y no objetos ... ¿hay un mejor patrón de implementación del que no estoy al tanto?

Por cierto, se me ocurrió una mejor solución para establecer las propiedades del objeto, pero quiero saber específicamente sobre el patrón de switch if - else vs en Objective-C


La implementación if-else que tiene es la forma correcta de hacerlo, ya que el switch no funcionará con los objetos. Aparte de que tal vez sea un poco más difícil de leer (lo cual es subjetivo), no hay una desventaja real en el uso if-else declaraciones if-else esta manera.


La refactorización más común sugerida para eliminar if-else o cambiar las declaraciones es la introducción de polimorfismo (ver http://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html ). La eliminación de dichos condicionales es más importante cuando están duplicados. En el caso del análisis XML como su muestra, básicamente está moviendo los datos a una estructura más natural para que no tenga que duplicar el condicional en otro lugar. En este caso, la instrucción if-else o switch es probablemente lo suficientemente buena.


Aunque no hay necesariamente una forma mejor de hacer algo así para un solo uso, ¿por qué usar "comparar" cuando puede usar "isEqualToString"? Eso parecería ser más eficaz ya que la comparación se detendría en el primer carácter no coincidente, en lugar de pasar por todo para calcular un resultado de comparación válido (aunque pensándolo bien, la comparación podría ser clara en el mismo punto) - aunque se vería un poco más limpio porque esa llamada devuelve un BOOL.

if([elementName isEqualToString:@"companyName"] ) [character setCorporationName:currentElementText]; else if([elementName isEqualToString:@"corporationID"] ) [character setCorporationID:currentElementText]; else if([elementName isEqualToString:@"name"] )


Debe aprovechar la codificación de clave-valor:

[character setValue:currentElementText forKey:elementName];

Si no se confía en los datos, es posible que desee verificar que la clave sea válida:

if (![validKeysCollection containsObject:elementName]) // Exception or error


En este caso, no estoy seguro si puede refactorizar fácilmente la clase para introducir el polimorfismo como sugiere Bradley, ya que es una clase nativa de Cocoa. En cambio, la forma de Objective-C para hacerlo es usar una categoría de clase para agregar un método elementNameCode a NSSting:

typedef enum { companyName = 0, companyID, ..., Unknown } ElementCode; @interface NSString (ElementNameCodeAdditions) - (ElementCode)elementNameCode; @end @implementation NSString (ElementNameCodeAdditions) - (ElementCode)elementNameCode { if([self compare:@"companyName"]==0) { return companyName; } else if([self compare:@"companyID"]==0) { return companyID; } ... { } return Unknown; } @end

En su código, ahora puede usar un interruptor en [elementName elementNameCode] (y obtener las advertencias del compilador asociadas si olvida probar para uno de los miembros enum, etc.).

Como Bradley señala, esto puede no valer la pena si la lógica solo se usa en un lugar.


Una forma en que he hecho esto con NSStrings es usando un NSDictionary y enumeraciones. Puede que no sea el más elegante, pero creo que hace que el código sea un poco más legible. El siguiente pseudocódigo se extrae de uno de mis proyectos :

typedef enum { UNKNOWNRESIDUE, DEOXYADENINE, DEOXYCYTOSINE, DEOXYGUANINE, DEOXYTHYMINE } SLSResidueType; static NSDictionary *pdbResidueLookupTable; ... if (pdbResidueLookupTable == nil) { pdbResidueLookupTable = [[NSDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithInteger:DEOXYADENINE], @"DA", [NSNumber numberWithInteger:DEOXYCYTOSINE], @"DC", [NSNumber numberWithInteger:DEOXYGUANINE], @"DG", [NSNumber numberWithInteger:DEOXYTHYMINE], @"DT", nil]; } SLSResidueType residueIdentifier = [[pdbResidueLookupTable objectForKey:residueType] intValue]; switch (residueIdentifier) { case DEOXYADENINE: do something; break; case DEOXYCYTOSINE: do something; break; case DEOXYGUANINE: do something; break; case DEOXYTHYMINE: do something; break; }


De hecho, hay una forma bastante simple de tratar con sentencias if-else en cascada en un lenguaje como Objective-C. Sí, puede usar la creación de subclases y la anulación, creando un grupo de subclases que implementan el mismo método de manera diferente, invocando la implementación correcta en tiempo de ejecución utilizando un mensaje común. Esto funciona bien si desea elegir una de las pocas implementaciones, pero puede generar una proliferación innecesaria de subclases si tiene muchas implementaciones pequeñas y ligeramente diferentes, como las que suele tener en sentencias largas if-else o switch.

En cambio, factorice el cuerpo de cada cláusula if / else-if en su propio método, todos en la misma clase. Nombra los mensajes que los invocan de manera similar. Ahora crea un NSArray que contenga los selectores de esos mensajes (obtenidos usando @selector ()). Fuerce la cadena que estaba probando en los condicionales en un selector usando NSSelectorFromString () (puede que necesite concatenar palabras adicionales o dos puntos en primer lugar dependiendo de cómo haya nombrado esos mensajes, y de si toman argumentos o no). Ahora haz auto-ejecutar el selector usando performSelector :.

Este enfoque tiene la desventaja de que puede saturar la clase con muchos mensajes nuevos, pero es probable que sea mejor agrupar una sola clase que toda la jerarquía de clases con nuevas subclases.


Si desea usar el menor código posible, y los nombres de sus elementos y setters están todos nombrados de modo que si elementName es @ "foo", setter está configurado como Fao :, podría hacer algo como:

SEL selector = NSSelectorFromString([NSString stringWithFormat:@"set%@:", [elementName capitalizedString]]); [character performSelector:selector withObject:currentElementText];

o posiblemente incluso:

[character setValue:currentElementText forKey:elementName]; // KVC-style

Aunque estos serán, por supuesto, un poco más lento que usar un montón de declaraciones if.

[Editar: La segunda opción ya fue mencionada por alguien; ¡Uy!]


Lo que hemos hecho en nuestros proyectos donde necesitamos para este tipo de cosas una y otra vez, es configurar un CFDictionary estático mapeando las cadenas / objetos para comparar con un valor entero simple. Esto lleva a un código que se ve así:

static CFDictionaryRef map = NULL; int count = 3; const void *keys[count] = { @"key1", @"key2", @"key3" }; const void *values[count] = { (uintptr_t)1, (uintptr_t)2, (uintptr_t)3 }; if (map == NULL) map = CFDictionaryCreate(NULL,keys,values,count,&kCFTypeDictionaryKeyCallBacks,NULL); switch((uintptr_t)CFDictionaryGetValue(map,[node name])) { case 1: // do something break; case 2: // do something else break; case 3: // this other thing too break; }

Si solo tiene como objetivo Leopard, puede usar una NSMapTable en lugar de una CFDictionary.


¿Me atrevo a sugerir usar una macro?

#define TEST( _name, _method ) / if ([elementName isEqualToString:@ _name] ) / [character _method:currentElementText]; else #define ENDTEST { /* empty */ } TEST( "companyName", setCorporationName ) TEST( "setCorporationID", setCorporationID ) TEST( "name", setName ) : : ENDTEST


Publicando esto como una respuesta a la respuesta de Wevah anterior: lo habría editado, pero todavía no tengo una reputación suficientemente alta:

desafortunadamente, el primer método se divide en campos con más de una palabra en ellos, como xPosition. capitalizedString lo convertirá en Xposition, que cuando se combina con el formato te da setXposition:. Definitivamente no es lo que se quería aquí. Esto es lo que estoy usando en mi código:

NSString *capName = [elementName stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[[elementName substringToIndex:1] uppercaseString]]; SEL selector = NSSelectorFromString([NSString stringWithFormat:@"set%@:", capName]);

No es tan bonito como el primer método, pero funciona.


Espero que todos ustedes me perdonen por haberlo hecho aquí, pero me gustaría abordar la cuestión más general de analizar documentos XML en Cocoa sin la necesidad de declaraciones de if-else. La pregunta tal como se estableció originalmente asigna el texto del elemento actual a una variable de instancia del objeto de carácter. Como jmah señaló, esto se puede resolver usando codificación de clave-valor. Sin embargo, en un documento XML más complejo esto podría no ser posible. Considera por ejemplo lo siguiente.

<xmlroot> <corporationID> <stockSymbol>EXAM</stockSymbol> <uuid>31337</uuid> </corporationID> <companyName>Example Inc.</companyName> </xmlroot>

Hay múltiples enfoques para lidiar con esto. Fuera de mi cabeza, puedo pensar en dos usando NSXMLDocument. El primero usa NSXMLElement. Es bastante sencillo y no involucra el tema if-else en absoluto. Simplemente obtiene el elemento raíz y revisa sus elementos nombrados uno por uno.

NSXMLElement* root = [xmlDocument rootElement]; // Assuming that we only have one of each element. [character setCorperationName:[[[root elementsForName:@"companyName"] objectAtIndex:0] stringValue]]; NSXMLElement* corperationId = [root elementsForName:@"corporationID"]; [character setCorperationStockSymbol:[[[corperationId elementsForName:@"stockSymbol"] objectAtIndex:0] stringValue]]; [character setCorperationUUID:[[[corperationId elementsForName:@"uuid"] objectAtIndex:0] stringValue]];

El siguiente utiliza el NSXMLNode más general, recorre el árbol y usa directamente la estructura if-else.

// The first line is the same as the last example, because NSXMLElement inherits from NSXMLNode NSXMLNode* aNode = [xmlDocument rootElement]; while(aNode = [aNode nextNode]){ if([[aNode name] isEqualToString:@"companyName"]){ [character setCorperationName:[aNode stringValue]]; }else if([[aNode name] isEqualToString:@"corporationID"]){ NSXMLNode* correctParent = aNode; while((aNode = [aNode nextNode]) == nil && [aNode parent != correctParent){ if([[aNode name] isEqualToString:@"stockSymbol"]){ [character setCorperationStockSymbol:[aNode stringValue]]; }else if([[aNode name] isEqualToString:@"uuid"]){ [character setCorperationUUID:[aNode stringValue]]; } } } }

Este es un buen candidato para eliminar la estructura if-else, pero al igual que el problema original, no podemos simplemente usar la caja del interruptor aquí. Sin embargo, aún podemos eliminar if-else utilizando performSelector. El primer paso es definir el método a para cada elemento.

- (NSNode*)parse_companyName:(NSNode*)aNode { [character setCorperationName:[aNode stringValue]]; return aNode; } - (NSNode*)parse_corporationID:(NSNode*)aNode { NSXMLNode* correctParent = aNode; while((aNode = [aNode nextNode]) == nil && [aNode parent != correctParent){ [self invokeMethodForNode:aNode prefix:@"parse_corporationID_"]; } return [aNode previousNode]; } - (NSNode*)parse_corporationID_stockSymbol:(NSNode*)aNode { [character setCorperationStockSymbol:[aNode stringValue]]; return aNode; } - (NSNode*)parse_corporationID_uuid:(NSNode*)aNode { [character setCorperationUUID:[aNode stringValue]]; return aNode; }

La magia ocurre en el método invokeMethodForNode: prefix: Generamos el selector basado en el nombre del elemento y realizamos ese selector con aNode como el único parámetro. Presto bango, hemos eliminado la necesidad de una declaración if-else. Aquí está el código para ese método.

- (NSNode*)invokeMethodForNode:(NSNode*)aNode prefix:(NSString*)aPrefix { NSNode* ret = nil; NSString* methodName = [NSString stringWithFormat:@"%@%@:", prefix, [aNode name]]; SEL selector = NSSelectorFromString(methodName); if([self respondsToSelector:selector]) ret = [self performSelector:selector withObject:aNode]; return ret; }

Ahora, en lugar de nuestra declaración más grande if-else (la que diferenciaba entre companyName y corporationID), simplemente podemos escribir una línea de código

NSXMLNode* aNode = [xmlDocument rootElement]; while(aNode = [aNode nextNode]){ aNode = [self invokeMethodForNode:aNode prefix:@"parse_"]; }

Ahora me disculpo si tengo algo de esto mal, ha pasado un tiempo desde que escribí algo con NSXMLDocument, es tarde en la noche y en realidad no probé este código. Entonces, si ve algo incorrecto, por favor deje un comentario o edite esta respuesta.

Sin embargo, creo que acabo de mostrar cómo los selectores con nombres adecuados se pueden usar en Cocoa para eliminar completamente las declaraciones de if-else en casos como este. Hay algunos errores y casos de esquina. El selector de rendimiento: familia de métodos solo toma 0, 1 o 2 métodos de argumento cuyos argumentos y tipos de retorno son objetos, por lo que si los tipos de los argumentos y el tipo de retorno no son objetos, o si hay más de dos argumentos, entonces usted tiene que usar una Invocación NS para invocarlo. Debe asegurarse de que los nombres de método que genere no invocarán otros métodos, especialmente si el destino de la llamada es otro objeto, y este esquema de nombres de método particular no funcionará en elementos con caracteres no alfanuméricos. Podría solucionarlo escapando de alguna manera de los nombres de los elementos XML, o construyendo un NSDictionary usando los nombres de los métodos como las claves y los selectores como valores. Esto puede consumir mucha memoria y terminar tardando más tiempo. El envío de performSelector como lo describí es bastante rápido. Para sentencias if-else muy grandes, este método puede ser incluso más rápido que una instrucción if-else.


He encontrado una solución que usa bloques para crear una estructura tipo interruptor para los objetos. Allí va:

BOOL switch_object(id aObject, ...) { va_list args; va_start(args, aObject); id value = nil; BOOL matchFound = NO; while ( (value = va_arg(args,id)) ) { void (^block)(void) = va_arg(args,id); if ( [aObject isEqual:value] ) { block(); matchFound = YES; break; } } va_end(args); return matchFound; }

Como puede ver, esta es una función C de la escuela antigua con una lista de argumentos variables. Paso el objeto a probar en el primer argumento, seguido de los pares case_value-case_block. (Recuerde que los bloques Objective-C son solo objetos.) El ciclo while sigue extrayendo estos pares hasta que el valor del objeto coincida o no queden casillas (vea las notas a continuación).

Uso:

NSString* str = @"stuff"; switch_object(str, @"blah", ^{ NSLog(@"blah"); }, @"foobar", ^{ NSLog(@"foobar"); }, @"stuff", ^{ NSLog(@"stuff"); }, @"poing", ^{ NSLog(@"poing"); }, nil); // <-- sentinel // will print "stuff"

Notas:

  • esta es una primera aproximación sin ningún error de comprobación
  • el hecho de que los manejadores de casos sean bloques requiere un cuidado adicional cuando se trata de la visibilidad, el alcance y la administración de la memoria de las variables a las que se hace referencia desde dentro
  • si te olvidas del centinela, estás condenado: P
  • puede usar el valor de retorno booleano para activar un caso "predeterminado" cuando ninguno de los casos ha sido correlacionado

Al igual que Lvsti, estoy usando bloques para realizar un patrón de conmutación en objetos.

Escribí una cadena basada en bloques de filtro muy simple, que toma n bloques de filtro y realiza cada filtro en el objeto.
Cada filtro puede alterar el objeto, pero debe devolverlo. No importa qué.

NSObject + Functional.h

#import <Foundation/Foundation.h> typedef id(^FilterBlock)(id element, NSUInteger idx, BOOL *stop); @interface NSObject (Functional) -(id)processByPerformingFilterBlocks:(NSArray *)filterBlocks; @end

NSObject + Functional.m

@implementation NSObject (Functional) -(id)processByPerformingFilterBlocks:(NSArray *)filterBlocks { __block id blockSelf = self; [filterBlocks enumerateObjectsUsingBlock:^( id (^block)(id,NSUInteger idx, BOOL*) , NSUInteger idx, BOOL *stop) { blockSelf = block(blockSelf, idx, stop); }]; return blockSelf; } @end

Ahora podemos configurar n FilterBlocks para probar los diferentes casos.

FilterBlock caseYES = ^id(id element, NSUInteger idx, BOOL *breakAfter){ if ([element isEqualToString:@"YES"]) { NSLog(@"You did it"); *breakAfter = YES; } return element; }; FilterBlock caseNO = ^id(id element, NSUInteger idx, BOOL *breakAfter){ if ([element isEqualToString:@"NO"] ) { NSLog(@"Nope"); *breakAfter = YES; } return element; };

Ahora pegamos esos bloques que queremos probar como una cadena de filtros en una matriz:

NSArray *filters = @[caseYES, caseNO];

y puede realizarlo en un objeto

id obj1 = @"YES"; id obj2 = @"NO"; [obj1 processByPerformingFilterBlocks:filters]; [obj2 processByPerformingFilterBlocks:filters];

Este enfoque se puede usar para cambiar pero también para cualquier aplicación de cadena de filtro (condicional), ya que los bloques pueden editar el elemento y pasarlo.