Substituir código de tipo por classe (da refatoração [Fowler])

9

Essa estratégia envolve a substituição de coisas como essa:

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; }
}

Com:

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 que exatamente isso é preferível a tornar o tipo uma enumeração, assim:

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

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

Não há comportamento associado ao tipo e, se houver, você usaria um tipo diferente de refatoração de qualquer maneira, por exemplo, 'Substituir código de tipo por subclasses' + 'Substituir condicional por polimorfismo'.

No entanto, o autor explica por que ele desaprova esse método (em Java?):

Códigos de tipo numérico, ou enumerações, são um recurso comum de linguagens baseadas em C. Com nomes simbólicos, eles podem ser bastante legíveis. O problema é que o nome simbólico é apenas um apelido; o compilador ainda vê o número subjacente. O tipo de compilador verifica usando o número 177 e não o nome simbólico. Qualquer método que usa o código de tipo como argumento espera um número e não há nada para forçar o uso de um nome simbólico. Isso pode reduzir a legibilidade e ser uma fonte de bugs.

Mas, ao tentar aplicar esta declaração ao C #, essa declaração não parece verdadeira: ela não aceita um número porque uma enumeração é realmente considerada uma classe. Portanto, o seguinte código:

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

Não será compilado. Portanto, essa refatoração pode ser considerada desnecessária em linguagens de alto nível mais recentes, como C #, ou não estou pensando em algo?

mérito
fonte
var temp = new Politician { MostNotableGrievance = MostNotableGrievance.Embezzlement };
Robert Harvey

Respostas:

6

Eu acho que você quase respondeu sua própria pergunta lá.

O segundo trecho de código é preferível ao primeiro porque oferece segurança de tipo. Com a primeira peça, se você tiver uma enumeração semelhante de Fish, poderá dizer algo como

MostNotableGrievance grievance = Fish.Haddock;

e o compilador não se importa. Se Fish.Haddock = 2, o acima será exatamente equivalente a

MostNotableGrievance grievance = MostNotableGrievance.FlipFlopping;

mas obviamente não é instantaneamente legível como tal.

A razão pela qual as enumerações geralmente não são melhores é porque a conversão implícita de e para int deixa você exatamente com o mesmo problema.

Mas, em C #, não existe essa conversão implícita; portanto, um enum é uma estrutura melhor para usar. Você raramente verá uma das duas primeiras abordagens usadas.

pdr
fonte
5

Se não houver comportamento associado ao valor e não houver informações extras que você precise armazenar ao lado dele e o idioma não suportar conversão implícita em números inteiros, eu usaria uma enumeração; é para isso que eles estão lá.

Telastyn
fonte
1

Outro exemplo que pode ajudá-lo pode ser encontrado em Java Efetivo (item 30, se você quiser procurá-lo). Também é válido para C #.

Suponha que você use constantes int para modelar animais diferentes, como cobras e cães.

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

int DOG_TERRIER=0;
int DOG_PITBULL=1;

Suponha que sejam constantes e talvez até definidas em diferentes classes. Isso pode parecer bom para você e pode realmente funcionar. Mas considere esta linha de código:

snake.setType(DOG_TERRIER);

Claramente, isso é um erro. O compilador não irá reclamar e o código será executado. Mas sua cobra não vai se transformar em um cachorro. Isso ocorre porque o compilador vê apenas:

snake.setType(0);

Sua cobra é na verdade um python (e não um terrier). Esse recurso é chamado de segurança de tipo e é realmente a razão pela qual você é um exemplo de código

var temp = new Politician { MostNotableGrievance = 1 };

não irá compilar. MostNotableGrievance é MostNotableGrievance e não um número inteiro. Com enums, você pode expressar isso no sistema de tipos. E é por isso que Martin Fowler e eu pensamos que as enumerações são ótimas.

scarfridge
fonte