Pregunta ¿Cuándo usar las enumeraciones y cuándo reemplazarlas con una clase con miembros estáticos?


Recientemente se me ocurrió que la siguiente enumeración (de muestra) ...

enum Color
{
    Red,
    Green,
    Yellow,
    Blue
}

... podría ser reemplazado con una clase aparentemente más segura para el tipo:

class Color
{
    private Color() { }

    public static readonly Color   Red      = new Color();
    public static readonly Color   Green    = new Color();
    public static readonly Color   Yellow   = new Color();
    public static readonly Color   Blue     = new Color();
}

Con "tipo seguro", quiero decir que la siguiente declaración funcionaría si Color era una enumeración, pero no si Color fueron la clase de arriba:

var nonsenseColor = (Color)17;    // works if Color is an enum

Dos preguntas:

1) ¿Existe un nombre ampliamente aceptado para este patrón (reemplazando una enumeración con una clase segura para tipos)?

2) ¿En qué casos debería uno usar enumeraciones, y cuándo una clase sería más apropiada?


32


origen


Respuestas:


Los mensajes son excelentes para información de estado ligero. Por ejemplo, su color de enumeración (excluyendo azul) sería bueno para consultar el estado de un semáforo. El verdadero color junto con todo el concepto de color y todo su bagaje (alfa, espacio de color, etc.) no importa, solo en qué estado está la luz. Además, cambiando su enumeración un poco para representar el estado del semáforo :

[Flags()]
public enum LightColors
{
    unknown = 0,
    red = 1,
    yellow = 2,
    green = 4,
    green_arrow = 8
}

El estado de luz actual se puede establecer como:

LightColors c = LightColors.red | LightColors.green_arrow;

Y consultado como:

if ((c & LightColors.red) == LightColors.red)
{
    //Don't drive
}
else if ((c & LightColors.green_arrow) == LightColors.green_arrow)
{
    //Turn
}

Los miembros de color de clase estáticos podrían admitir este estado múltiple sin funcionalidad adicional.

Sin embargo, los miembros de la clase estáticos son maravillosos para los objetos de uso común. los System.Drawing.Color los miembros son excelentes ejemplos, ya que representan colores de nombre conocido que tienen constructores oscuros (a menos que conozcas tus colores hexadecimales). Si se implementaran como enumeraciones, tendrías que hacer algo como esto cada vez que quisieras usar el valor como color:

colors c = colors.red;
switch (c)
{
    case colors.red:
        return System.Drawing.Color.FromArgb(255, 0, 0);
        break;
    case colors.green:
        return System.Drawing.Color.FromArgb(0,255,0);
        break;
}

Entonces, si tiene una enumeración y encuentra que está constantemente haciendo un cambio / caso / if / else / lo que sea para derivar un objeto, es posible que desee usar miembros estáticos de la clase. Si solo estás consultando el estado de algo, me quedaré con enumeraciones. Además, si tiene que pasar los datos de una manera insegura, las enumeraciones probablemente sobrevivirán mejor que una versión serializada de su objeto.

Editar: @stakx, creo que tropezó con algo importante, también en respuesta a la publicación de @ Anton y que es complejidad o, lo que es más importante, ¿para quién es compleja?

Desde el punto de vista del consumidor, preferiría inmensamente que System.Drawing.Color tenga miembros estáticos de clase sobre tener que escribir todo eso. Desde el punto de vista del productor, sin embargo, sería un dolor tener que escribir todo eso. Por lo tanto, si otras personas van a usar su código, es posible que les ahorre muchos problemas al usar miembros de clase estáticos, aunque puede tomar 10 veces más tiempo escribir / probar / depurar. Sin embargo, si es solo usted, le resultará más fácil simplemente usar enumeraciones y conversión / conversión según sea necesario.


22



De hecho, he estado luchando con algo como esto bastante en el trabajo.

Estaba basado en un ejemplo que fue publicado por John B en El artículo de blog de Jon Skeet "Enumizaciones mejoradas en C #".

Lo que no conseguí que funcionara correctamente sin MUCHA fealdad era extender una enumeración derivando una clase de enumeración base y añadiendo campos estáticos adicionales del tipo de derivación.

Si revisa los conceptos básicos que se publican en ese blog, notará que las clases derivadas compartirán un conjunto común de valores que tienen problemas serios de tipado y cuando una determinada clase base de enumeración se deriva en dos clases diferentes, sus conjuntos de valores estarán en la misma colección también

Quiero decir, sería difícil crear una variable de DerivedEnumClass1 y tener que almacenar un valor de su colección de enumeración que en realidad es de tipo BaseEnumClass1 en esa variable sin soluciones provisionales.

Otro problema fue la serialización Xml que usamos mucho. Entendí eso usando dos clases genéricas como tipos de datos en variables de enumeración en nuestros objetos comerciales: uno que representaba un valor de enumeración único y otro que representaba un conjunto de valores de enumeración que utilicé para imitar el comportamiento de enumeración de indicadores. Controlaron la serialización xml y la deserialización de los valores reales que estaban almacenados en las propiedades que representaban. Un par de sobrecargas de operador era todo lo que era necesario para recrear el comportamiento bitflag de esta manera.

Esas son algunas de las principales cuestiones con las que me encontré.

En general, no estoy muy satisfecho con el resultado final, algunas cosas malolientes, pero hace el trabajo por ahora.

Por qué nuestro arquitecto en jefe decidió intentarlo aún para que funcione, fue la posibilidad de tener clases reales y ampliarlas con todo tipo de comportamiento, ya sea genérico o específico. Hasta ahora no he visto mucho que no hubiera podido proporcionar con los métodos de extensión, pero podríamos encontrarnos con algo.

No tengo ningún código aquí y no podré llegar a él durante el fin de semana, de lo contrario podría haber mostrado dónde lo había llevado. Aunque no es muy bonito ...: o


3



Algunas cosas que encontré mientras tanto, si alguien más está interesado:

  • switch los bloques no funcionarán con una enumeración como clase.

  • Usuario empi mencionó la similitud de la muestra enum-as-class de arriba con las enumeraciones de Java. Parece que en el mundo de Java, hay un patrón reconocido llamado Enum tipo seguro; Al parecer, este patrón se remonta al libro de Joshua Bloch Java efectivo.


2



Usaría las constantes de clase cuando tuviera que interoperar con algún código heredado que quisiera algunos valores oscuros de indicadores de bit que podrían estar juntos. En este caso, la enumeración podría parecerse

public enum Legacy : ushort
{
  SomeFlag1 = 0x0001,
  SomeFlag2 = 0x0002,
  // etc...
}

Entonces la clasificación en la P / Invoke es menos legible debido a la conversión necesaria para traducir la enumeración al valor apropiado.

Si fuera una variable de clase const, no se necesitaría un elenco.


1



Ambos enfoques son válidos. Debe elegir por caso

Puedo agregar que las enum admiten operaciones de bits como banderas (incluso hay un atributo [Flags] para soportar esta semántica y producir cadenas bonitas desde enums).

Hay una refactorización muy similar llamada Reemplazar enumeraciones con el patrón de estrategia. Bueno, en tu caso no es del todo completo, ya que tu color es pasivo y no actúa como una estrategia. Sin embargo, ¿por qué no pensar en él como un caso especial?


1



Sí, la edición original de "Effective Java" de Joshua Bloch, que se lanzó antes de Java 1.5 y el soporte nativo para enumeraciones en Java, demostró el Typesafe Enum patrón. Desafortunadamente, la última versión del libro se dirige a Java> 1.5, por lo que utiliza en lugar de Java.

En segundo lugar, no se puede convertir de int a enum en Java.

Color nonsenseColor = (Color)17; // compile-error

Aunque no puedo hablar en otros idiomas.


0