Reemplazar código de tipo con clase (de refactorización [Fowler])

9

Esta estrategia implica reemplazar los gustos de esto:

public class Politician
{
    public const int Infidelity = 0;
    public const int Embezzlement = 1;
    public const int FlipFlopping = 2;
    public const int Murder = 3;
    public const int BabyKissing = 4;

    public int MostNotableGrievance { get; set; }
}

Con:

public class Politician
{
    public MostNotableGrievance MostNotableGrievance { get; set; }
}

public class MostNotableGrievance
{
    public static readonly MostNotableGrievance Infidelity = new MostNotableGrievance(0);
    public static readonly MostNotableGrievance Embezzlement = new MostNotableGrievance(1);
    public static readonly MostNotableGrievance FlipFlopping = new MostNotableGrievance(2);
    public static readonly MostNotableGrievance Murder = new MostNotableGrievance(3);
    public static readonly MostNotableGrievance BabyKissing = new MostNotableGrievance(4);

    public int Code { get; private set; }

    private MostNotableGrievance(int code)
    {
        Code = code;
    }
}

¿Por qué es exactamente esto preferible a hacer que el tipo sea una enumeración, así:

public class Politician
{
    public MostNotableGrievance MostNotableGrievance { get; set; }
}

public enum MostNotableGrievance
{
    Infidelity = 0,
    Embezzlement = 1,
    FlipFlopping = 2,
    Murder = 3,
    BabyKissing = 4
}

No hay ningún comportamiento asociado con el tipo y si lo hubiera, estaría utilizando un tipo diferente de refactorización de todos modos, por ejemplo, 'Reemplazar código de tipo con subclases' + 'Reemplazar condicional con polimorfismo'.

Sin embargo, el autor explica por qué desaprueba este método (¿en Java?):

Los códigos de tipo numérico, o enumeraciones, son una característica común de los lenguajes basados ​​en C. Con nombres simbólicos pueden ser bastante legibles. El problema es que el nombre simbólico es solo un alias; el compilador aún ve el número subyacente. El tipo de compilador verifica usando el número 177, no el nombre simbólico. Cualquier método que tome el código de tipo como argumento espera un número, y no hay nada que obligue a usar un nombre simbólico. Esto puede reducir la legibilidad y ser una fuente de errores.

Pero cuando se trata de aplicar esta declaración a C #, esta declaración no parece ser verdadera: no aceptará un número porque una enumeración se considera en realidad una clase. Entonces el siguiente código:

public class Test
{
    public void Do()
    {
        var temp = new Politician { MostNotableGrievance = 1 };
    }
}

No se compilará. Entonces, ¿puede esta refactorización considerarse innecesaria en los nuevos lenguajes de alto nivel, como C #, o no estoy considerando algo?

Merritt
fuente
var temp = new Politician { MostNotableGrievance = MostNotableGrievance.Embezzlement };
Robert Harvey

Respuestas:

6

Creo que casi has respondido tu propia pregunta allí.

Se prefiere el segundo código sobre el primero porque ofrece seguridad de tipo. Con la primera pieza, si tiene una enumeración similar de Fish, entonces puede decir algo como

MostNotableGrievance grievance = Fish.Haddock;

Y al compilador no le importará. Si Fish.Haddock = 2, lo anterior será exactamente equivalente a

MostNotableGrievance grievance = MostNotableGrievance.FlipFlopping;

pero obviamente no es legible al instante como tal.

La razón por la cual las enumeraciones a menudo no son mejores es porque la conversión implícita ay desde int le deja exactamente el mismo problema.

Pero, en C #, no existe tal conversión implícita, por lo que una enumeración es una mejor estructura para usar. Raramente verá ninguno de los dos primeros enfoques utilizados.

pdr
fuente
5

Si no hay un comportamiento asociado con el valor y no hay información adicional que necesita almacenar junto con él y el lenguaje no admite la conversión implícita a enteros, usaría una enumeración; para eso están allí.

Telastyn
fuente
1

Otro ejemplo que podría ayudarlo se puede encontrar en Effective Java (elemento 30, si desea buscarlo). También es válido para C #.

Suponga que usa constantes int para modelar diferentes animales, digamos serpientes y perros.

int SNAKE_PYTHON=0;
int SNAKE_RATTLE=1;
int SNAKE_COBRA=2;

int DOG_TERRIER=0;
int DOG_PITBULL=1;

Suponga que estas son constantes y tal vez incluso definidas en diferentes clases. Esto puede parecerle bien y en realidad podría funcionar. Pero considere esta línea de código:

snake.setType(DOG_TERRIER);

Claramente, esto es un error. El compilador no se quejará y el código se ejecutará. Pero tu serpiente no se convertirá en un perro. Eso es porque el compilador solo ve:

snake.setType(0);

Tu serpiente es en realidad una pitón (y no un terrier). Esta característica se llama seguridad de tipo y en realidad es la razón por la cual eres un código de ejemplo

var temp = new Politician { MostNotableGrievance = 1 };

no se compilará MostNotableGrievance es MostNotableGrievance, no un número entero. Con enumeraciones puede expresar esto en el sistema de tipos. Y es por eso que Martin Fowler y yo pensamos que las enumeraciones son geniales.

Scarfridge
fuente