¿Cuándo debo usar una clase de 2 propiedades sobre una estructura preconstruida como KeyValuePair?

31

¿Cuándo debe colocar el tipo de datos Clave / Valor en su propia clase en lugar de utilizar una estructura genérica preconstruida, como a KeyValuePairo a Tuple?

Por ejemplo, la mayoría de los ComboBoxes que creo contienen un DisplayName y un Value. Este es el tipo de datos que estoy tratando de decidir cuándo incluir una nueva clase y cuándo usar un KeyValuePair.

Actualmente estoy trabajando en algo que usa iCalendar, y los datos del usuario seleccionado finalmente se combinan en un key1=value1;key2=value2;tipo de cadena. Comencé colocando los datos en a KeyValuePair<string,string>, pero ahora me pregunto si esa debería ser su propia clase.

En general, estoy interesado en averiguar qué pautas se utilizan al decidir usar una estructura / clase existente como un objeto KeyValuePairsobre dos propiedades, y en qué tipo de situaciones usaría una sobre otra.

Rachel
fuente
3
¿Que lenguaje? En Python, no tenemos este dilema, porque tenemos tupletipo.
S.Lott
3
@ S.Lott: el .NET BCL tiene tuplas desde la versión 4.0.
Oded el
1
.NET 4 también tiene el tipo de tupla. msdn.microsoft.com/en-us/library/system.tuple.aspx
Dave Nay
1
@Oded En este caso, estoy construyendo algo con iCalendary quería objetos para BYDAYy BYSETPOS. Aparecen en los cuadros combinados, pero los datos reales se combinan en la cadena de regla recurrente, que es un key=value;tipo de cadena
Rachel
44
@Rachel: Por favor, actualice la cuestión de ser más específico. Un montón de comentarios no son la mejor manera de aclarar las cosas.
S.Lott

Respuestas:

26

Por lo general, usaría un objeto en lugar de un KeyValuePair o Tuple en la mayoría de los casos. Primero, cuando vienes 6 meses después para hacer cambios, es mucho más fácil descubrir cuál era tu intención antes que preguntarte qué Tuple tes y por qué tiene esos valores divertidos. En segundo lugar, a medida que las cosas crecen y cambian, puede facilitar el comportamiento de sus objetos de transferencia de datos simples según sea necesario. ¿Necesita tener dos formatos de nombre? Fácil, solo agregue las sobrecargas ToString () apropiadas. ¿Lo necesita para implementar alguna interfaz? No hay problema. Finalmente, realmente hay casi cero gastos generales para crear un objeto simple, especialmente con propiedades automáticas y finalización de código.

Protip de bonificación: si desea evitar que estos objetos contaminen su espacio de nombres, crear clases privadas dentro de las clases es una excelente manera de mantener las cosas en secreto y evitar dependencias extrañas.

Wyatt Barnett
fuente
1
DTO = ¿ Objetos de transferencia de datos ? - Odio los TLA ;-)
Ben
3
@Ben - TFTFY. . .
Wyatt Barnett
7

La regla para definir una nueva clase es simple: se resume en "Navaja de Occam".

http://c2.com/cgi/wiki?OccamsRazor

No introduzca nuevas clases sin una buena razón. O use clases preconstruidas tanto como sea posible. O invente lo menos posible. Sin embargo, te sientes cómodo escribiendo menos código.

No puede usar una clase preconstruida si tiene que asignar una responsabilidad única al objeto y esa responsabilidad no está definida en la clase preconstruida. A menudo este es un método único.

A tupleo KeyValuePairse prefiere.

Hasta.

Necesita alguna funcionalidad que no sea parte de A tupleo KeyValuePair. Entonces tienes que definir tu propia clase.

S.Lott
fuente
3
" Nunca diseñes lo que puedes robar " Se trata de mi forma favorita de redactar esto.
SingleNegationElimination
1
¿Considerarías que la semántica es "funcionalidad que no es parte de una tupleo KeyValuePair"? KeyValuePair al menos tiene alguna forma semántica limitada, pero las tuplas rara vez tienen (podría ser posible, según el contexto) imho. Introducir una nueva clase debería darle una semántica clara como algo natural.
Mal Ross el
+1 No parches un todo en un bote con cinta adhesiva si cabe en un enchufe.
Evan Plaice
44
Creo que este es un uso fuera de lugar de la navaja de Occam. En segundo lugar, es importante saber qué información contiene una variable. -1.
Doblado
4

Si escribe su propia clase, tiene un lugar claro para colocar la documentación sobre cuáles son los dos valores. Sobre todo porque dos cadenas no es una definición muy clara. Sin embargo, podrías escribir algo como

 public class MyPair : Tuple<string, string>
Firmar
fuente
Me gusta esto porque MyPairdefine cuáles son los valores de Tuple y para qué se usan.
IAbstract
2
Pero entonces sus propiedades se llamarían Item1y Item2! ¿No es tan fácil definir toda la clase? public class SideStrings { public string Left { get; set; } public string Right { get; set; } }
configurador
@configurator Estaba escribiendo una respuesta a eso y noté que Tuple es de solo lectura, así que estoy de acuerdo con lo que creo que es discutible. Aunque podría ajustar la tupla para nombrar los valores que desee.
Firmar el
2
@Sign: ¿Pero cuál es el punto? Crear tu propia clase es menos trabajo que envolver una tupla.
configurador
2
@configurator obtienes algunas cosas de igualdad y comparación que funcionan mejor que un objeto personalizado, pero si se requiere una configuración, la tupla es definitivamente el camino equivocado.
Firmar el
4

S.Lott escribió

No introduzca nuevas clases sin una buena razón. O use clases preconstruidas tanto como sea posible. Inventar lo menos posible.

No puede usar una clase preconstruida si tiene que asignar una responsabilidad única al objeto que no está definido en la clase preconstruida.

Permítanme decir por qué tengo un problema grave con esto. Ya que

KeyValuePair [cadena, cadena]

parece estar bien ... ¿KeyValue [string, KeyValue [string, KeyValuePair [string, string]]] también está bien? No sé qué dirá S.Lott a esto, pero mi jefe piensa que está bien.

Me atrevo a estar en desacuerdo. Y aquí está el por qué: reduce la legibilidad del código que seguirá. La función que llenará dicha estructura de datos será mucho más compleja y propensa a errores (err .. excepción) (solo imagine al menos algo de lógica comercial también). No discutí demasiado con mi jefe, pero lo diré aquí: la legibilidad supera el ahorro de espacio minuto (que fue su argumento). Entonces, ¿no sería un objeto de clase en el que hay algunos campos; pero algunos permanecen vacíos en algunas ocasiones ¿será mejor?

PD: Soy un Ultra_Noob, así que dime si estoy equivocado.

Chani
fuente
2
Creo que KeyValuePair<string,string>está bien, suponiendo que las cosas puestas sean claves y valores. Aquí, reutilizar una clase existente es una ganancia, ya que guarda el código de escritura y gana interoperabilidad. OTOH, usar algo tan complicado como la KeyValuePair<string,KeyValuePair<string,string>>mayoría de las veces es simplemente demasiado complicado para ser práctico. A veces puede ser correcto, cuando no necesita funcionalidad adicional, cuando el significado es obvio y cuando funciona bien con otras clases. YMMV, esto es solo ponderar los pros y los contras.
maaartinus
Si está utilizando C #, aquí está el rendimiento de un Tuple vs KeyValuePair . ¿Algo que quieras compartir con tu jefe?
Ben
3

Cuando decimos par clave / valor , generalmente pienso en tablas hash , matrices asociativas o simplemente un objeto KeyValuePair . Los uso todos y no hay diferencia en cuándo uso cuáles.

Creo que lo más importante de una lista de pares clave / valor es que las claves deberían ser únicas (ya que es una clave después de todo), y todas las estructuras antes mencionadas realmente me proporcionan esa funcionalidad.

Aparte de eso, quiero buscar por teclas o hacer acciones de estilo de lista (matriz) como push y pop.

Entonces, mi respuesta es NO , y no creo objetos explícitamente para pares clave / valor, ya que muchas estructuras incorporadas ya lo hacen por mí.

Saeed Neamati
fuente
3

El capítulo seis de Clean Code tiene una buena descripción de cuándo usar una estructura de datos versus una clase. En pocas palabras, utiliza una estructura de datos cuando anticipa agregar nuevas funciones para operar con esos datos. Usas una clase cuando anticipas agregar nuevos tipos de datos para ser operados por las funciones existentes. Su ComboBox es un ejemplo de esto último. Tiene un conjunto de funciones (la implementación de ComboBox) que opera en muchos tipos de datos diferentes (diferentes tipos de datos para diferentes widgets).

Karl Bielefeldt
fuente
2

Quizás estaría de acuerdo con Wilding aquí , pero también soy un poco novato en este tipo de cosas.

Sugeriría que los tipos incorporados son a menudo objetos de mecanismo . KeyValuePair, por ejemplo, es parte del mecanismo de un diccionario y tablas hash, lo que ayuda a garantizar claves únicas y tienen propiedades que tienen nombres significativos dentro del contexto del mecanismo en sí.

Por otro lado, el uso de objetos de datos personalizados proporciona una mejor representación de sus datos y proporciona muchos beneficios, incluida la legibilidad y la extensibilidad. No hay nada que deje de reutilizar el código existente en los objetos personalizados creados como Sign sugirió , pero trataría de ocultar la clase envuelta y evitaría la fuga de abstracción , para que pueda cambiar la implementación subyacente en un punto posterior sin afectar el resto de su código .

Entonces, la verdadera pregunta: ¿está representando datos o los está utilizando en un mecanismo? (y no recomendaría mezclar estos conceptos juntos).

En mi experiencia, no hay nada peor que ver pasar una Tuple [cadena, cadena] a un método y no tener una forma inmediata de saber qué se supone que son el Elemento1 y el Elemento2. Es mejor usar Tuples dentro de métodos o como miembros privados de una clase solamente.

Ben
fuente