2009-03-23 18 views
42

La base de código donde trabajo tiene un objeto llamado Pair donde A y B son los tipos del primer y segundo valor en el Pair. Encuentro que este objeto es ofensivo, porque se usa en lugar de un objeto con miembros claramente nombrados. Así que me parece esto:¿Por qué usar tuplas en lugar de objetos?

List<Pair<Integer, Integer>> productIds = blah(); 
// snip many lines and method calls 

void doSomething(Pair<Integer, Integer> id) { 
    Integer productId = id.first(); 
    Integer quantity = id.second(); 
} 

En lugar de

class ProductsOrdered { 
    int productId; 
    int quantityOrdered; 
    // accessor methods, etc 
} 

List<ProductsOrderded> productsOrdered = blah(); 

Muchos otros usos de la pareja en el código base son igualmente mal olor.

Busqué tuplas en Google y parece que a menudo se las malinterpreta o se las usa de forma dudosa. ¿Existe un argumento convincente a favor o en contra de su uso? Puedo apreciar que no quiero crear grandes jerarquías de clases, pero ¿hay bases de código realistas donde explotaría la jerarquía de clases si no se usaran las tuplas?

Respuesta

36

Antes que nada, una tupla es rápida y fácil: en vez de escribir una clase para cada vez que quiere poner 2 cosas juntas, hay una plantilla que lo hace por usted.

En segundo lugar, son genéricos. Por ejemplo, en C++, std :: map usa un std :: par de clave y valor. Por lo tanto, se puede usar CUALQUIER par, en lugar de tener que hacer algún tipo de clase de contenedor con métodos de acceso para cada permutación de dos tipos.

Finalmente, son útiles para devolver múltiples valores. Realmente no hay motivo para crear una clase específica para los valores de retorno múltiples de una función, y no deberían tratarse como un solo objeto si no están relacionados.

Para ser justos, el código que pegaste es un mal uso de un par.

+4

¿Está jugando al abogado del diablo aquí, pero hay realmente muchos casos en los que necesita un método que devuelve múltiples valores no relacionados? El único escenario que veo bastante a menudo es el método 'TryParse' para los tipos de datos incorporados en C#. Aquí, debe devolver un 'bool' (si el análisis fue exitoso) y, por ejemplo, un' int' (el valor si el análisis fue exitoso). Para permitir un segundo valor de retorno, se usa la palabra clave 'out'. Supongo que una tupla podría ser agradable aquí, pero no estoy seguro de si es mejor que declarar un tipo con un 'bool' llamado' ParseSuccessful' y una 'T' genérica llamada' Value'. – devuxer

+1

@DanThMan: sí, aunque no están relacionados. Por ejemplo, el algoritmo de C++ 'equal_range' devuelve un par de iteradores. Los mapas en C++ son equivalentes a los conjuntos del 'par '. Hay otros casos, pero no puedo pensar en ellos ahora mismo. – rlbond

+1

DanThMan: viniendo de la dirección opuesta (un lenguaje que admite múltiples valores de retorno), uno podría preguntar lo mismo: ¿alguna vez necesita parámetros de "salida" si tiene múltiples valores devueltos? ¿Quieres * devolver * 2 cosas, entonces, ¿por qué una parece que la estoy pasando? :-) – Ken

2

Este es solo un código de calidad prototipo que probablemente se rompió y nunca se ha refactorizado. No arreglarlo es solo pereza.

El uso real para las tuplas es para la funcionalidad genérica que realmente no le importa cuáles son los componentes, sino que simplemente funciona en el nivel de tupla.

+0

Josh: ¿Qué código va a escribir para solucionar esto? – shahkalpesh

+0

Exactamente lo que sugirió que funcionaría. Sin ver más contexto, no puedo comentar realmente sobre eso. – Eclipse

16

Las tuplas se usan todo el tiempo en Python, donde se integran en el idioma y son muy útiles (permiten múltiples valores de retorno para los principiantes).

A veces, realmente solo necesitas emparejar cosas y crear una clase real, honesta para Dios, es exagerada. Por otro lado, usar tuplas cuando realmente deberías usar una clase es una idea tan mala como la inversa.

+1

+1 devolver valores múltiples es la gran razón para mí. Una clase de par es, probablemente, un buen compromiso entre la devolución del objeto [] o un tipo completamente nuevo. –

1

El ejemplo obvio es un par de coordenadas (o triple). Las etiquetas son irrelevantes; usar X e Y (y Z) es solo una convención. Hacerlos uniformes deja en claro que se los puede tratar de la misma manera.

+0

@JohnGibb: se espera que los elementos en un 'Tuple' tengan diferentes significados. Es por eso que debe especificar sus tipos individualmente: 'Item1' puede ser una cadena pero' Item2' un entero, etc. –

+0

De hecho, 'x' e' y' en realidad tienen simetría (generalmente en un sistema de coordenadas tratamos cada dimensión de forma idéntica), por lo que tendría aún más sentido dar un paso más y ponerlos en una matriz. –

8

Por lo que vale, el código en el OP es un desastre no porque use tuplas, sino porque los valores en la tupla son demasiado débilmente tipados. Compare los siguientes:

List<Pair<Integer, Integer>> products_weak = blah1(); 
List<Pair<Product, Integer>> products_strong = blah2(); 

me altero demasiado si mi equipo de desarrollo se pasa alrededor de los ID en lugar de instancias de clases de todo, debido a que un número entero puede representar cualquier cosa .


Con eso se dice, tuplas son extremadamente útiles cuando se utilizan derecha:

  • Tuplas existen para agrupar los valores ad hoc juntos. Ciertamente son mejores que crear un número excesivo de clases de contenedor.
  • Alternativa útil a los parámetros out/ref cuando necesita devolver más de un valor de una función.

Sin embargo, las tuplas en C# hacen que mis ojos se vuelvan agua. Muchos lenguajes como OCaml, Python, Haskell, F #, etc. tienen una sintaxis especial y concisa para definir tuplas. Por ejemplo, en F #, el Map module define un constructor como sigue:

val of_list : ('key * 'a) list -> Map<'key,'a> 

I puede crear una instancia de un mapa usando:

(* val values : (int * string) list *) 
let values = 
    [1, "US"; 
    2, "Canada"; 
    3, "UK"; 
    4, "Australia"; 
    5, "Slovenia"] 

(* val dict : Map<int, string> *) 
let dict = Map.of_list values 

El código equilvalent en C# es ridicuous:

var values = new Tuple<int, string>[] { 
    new Tuple<int, string>(1, "US"), 
    new Tuple<int, string>(2, "Canada"), 
    new Tuple<int, string>(3, "UK"), 
    new Tuple<int, string>(4, "Australia"), 
    new Tuple<int, string>(5, "Slovenia") 
    } 

var dict = new Dictionary<int, string>(values); 

No creo que haya nada de malo en las tuplas en principio, pero la sintaxis de C# es demasiado engorrosa para sacar el máximo provecho del metro.

+0

Aunque puedes limpiarlo un poco. ¿Qué tal esta clase de ayuda? clase estática Tuple {Tuple estática compilación (T0 t0, T1 t1); } Luego puede crear una tupla como la siguiente: Tuple.Build (1, "US"); Aprovecha que los tipos genéricos se deducen para las funciones. :) – jalf

+0

Todavía no está tan limpio como en Python o ML/F #, pero al menos evita tener que especificar los parámetros genéricos – jalf

+0

Su código de tupla aquí podría hacerse con un diccionario usando los inicializadores de diccionario. (nuevo Dictionary () {{1, "US"} {2, "Canada"}}). ToTuple()? –

1

Ya se han mencionado muchas cosas, pero creo que también se debe mencionar que existen algunos estilos de programación que difieren de los POO y que son bastante útiles.

Los lenguajes de programación funcional como Haskell, por ejemplo, no tienen clases en absoluto.

10

El ejemplo de código tiene unos olores diferentes:

  • reinventar la rueda

Ya existe una tupla disponible en el marco; la estructura KeyValuePair. Esto es usado por la clase Dictionary para almacenar pares, pero puede usarlo en cualquier lugar que le quede. (No digo que encaja en este caso ...)

  • Haciendo una rueda cuadrada

Si usted tiene una lista de pares que es mejor usar la estructura KeyValuePair de una clase con el mismo propósito , ya que resulta en menos asignaciones de memoria.

  • ocultar la intención

Una clase con propiedades muestra claramente lo que significan los valores, mientras que una clase Pair<int,int> no le dice nada acerca de lo que los valores representan (sólo que probablemente están relacionados de alguna manera) . Para que el código sea razonablemente explicativo con una lista como esa, debería darle a la lista un nombre muy descriptivo, como productIdAndQuantityPairs ...

4

Es la reutilización del código. En lugar de escribir otra clase con exactamente la misma estructura que las últimas 5 clases similares a tuplas que hicimos, usted hace ...una clase Tuple, y úsala siempre que necesites una tupla.

Si el único significado de la clase es "almacenar un par de valores", entonces diría que usar tuplas es una idea obvia. Yo diría que fue un olor a código (por mucho que odie el término) si comenzaste a implementar múltiples clases idénticas solo para que puedas cambiar el nombre de los dos miembros.

0

Si estás haciendo un Schwartzian transform para ordenar por una llave especial (que es caro para calcular repetidamente), o algo así, una clase parece ser un poco exagerado:

val transformed = data map {x => (x.expensiveOperation, x)} 
val sortedTransformed = transformed sort {(x, y) => x._1 < y._1} 
val sorted = sortedTransformed map {case (_, x) => x} 

Tener un class DataAndKey o lo que sea que parezca un poco superfluo aquí.

Sin embargo, su ejemplo no era un buen ejemplo de una tupla, estoy de acuerdo.

2

Scala tiene tipos de tuplas, desde 2 hasta 5 tuplas hasta tuplas con más de 20 elementos. Ver First Steps to Scala (paso 9):

val pair = (99, "Luftballons") 
println(pair._1) 
println(pair._2) 

Las tuplas son útiles si necesita agrupar juntos los valores para algunos relativamente especial propósito. Por ejemplo, si tiene una función que necesita devolver dos objetos que no están relacionados, en lugar de crear una nueva clase para contener los dos objetos, devuelve un par de la función.

Estoy completamente de acuerdo con otros carteles que las tuplas pueden ser mal utilizadas. Si una tupla tiene algún tipo de semántica importante para su aplicación, debe usar una clase adecuada en su lugar.

Cuestiones relacionadas