Remplacer le code de type par la classe (à partir de la refactorisation [Fowler])

9

Cette stratégie consiste à remplacer les goûts de ceci:

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

Avec:

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

Pourquoi est-ce préférable à faire du type une énumération, comme ceci:

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

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

Il n'y a aucun comportement associé au type et s'il y en avait, vous utiliseriez de toute façon un type de refactorisation différent, par exemple, 'Remplacer le code de type par des sous-classes' + 'Remplacer le conditionnel par un polymorphisme'.

Cependant, l'auteur explique pourquoi il fronce les sourcils sur cette méthode (en Java?):

Les codes de type numérique, ou énumérations, sont une caractéristique courante des langages basés sur C. Avec des noms symboliques, ils peuvent être tout à fait lisibles. Le problème est que le nom symbolique n'est qu'un alias; le compilateur voit toujours le nombre sous-jacent. Le type de compilateur vérifie en utilisant le nombre 177 et non le nom symbolique. Toute méthode qui prend le code de type comme argument attend un nombre et rien ne force l'utilisation d'un nom symbolique. Cela peut réduire la lisibilité et être une source de bugs.

Mais lorsque vous essayez d'appliquer cette déclaration à C #, cette déclaration ne semble pas être vraie: elle n'acceptera pas de nombre car une énumération est en fait considérée comme une classe. Donc, le code suivant:

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

Ne compile pas. Cette refactorisation peut-elle être considérée comme inutile dans les nouveaux langages de haut niveau, comme C #, ou ne suis-je pas en train de penser à quelque chose?

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

Réponses:

6

Je pense que vous avez presque répondu à votre propre question.

Le deuxième morceau de code est préféré au premier car il offre une sécurité de type. Avec la première pièce, si vous avez une énumération similaire de poisson, vous pouvez dire quelque chose comme

MostNotableGrievance grievance = Fish.Haddock;

et le compilateur ne s'en souciera pas. Si Fish.Haddock = 2, alors ce qui précède sera exactement équivalent à

MostNotableGrievance grievance = MostNotableGrievance.FlipFlopping;

mais évidemment pas immédiatement lisible comme tel.

La raison pour laquelle les énumérations ne sont souvent pas meilleures est que la conversion implicite vers et depuis int vous laisse exactement le même problème.

Mais, en C #, il n'y a pas une telle conversion implicite, donc une énumération est une meilleure structure à utiliser. Vous verrez rarement l'une des deux premières approches utilisées.

pdr
la source
5

S'il n'y a aucun comportement associé à la valeur et qu'il n'y a aucune information supplémentaire que vous devez stocker à côté d'elle et que le langage ne prend pas en charge la conversion implicite en nombres entiers, j'utiliserais une énumération; c'est pour ça qu'ils sont là.

Telastyn
la source
1

Un autre exemple qui pourrait vous aider peut être trouvé dans Java efficace (élément 30, si vous voulez le rechercher). Il est également valable pour C #.

Supposons que vous utilisez des constantes int pour modéliser différents animaux, par exemple des serpents et des chiens.

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

int DOG_TERRIER=0;
int DOG_PITBULL=1;

Supposons que ce sont des constantes et peut-être même définies dans différentes classes. Cela peut vous sembler correct et cela pourrait fonctionner. Mais considérez cette ligne de code:

snake.setType(DOG_TERRIER);

Il s'agit clairement d'une erreur. Le compilateur ne se plaindra pas et le code s'exécutera. Mais votre serpent ne se transformera pas en chien. C'est parce que le compilateur ne voit que:

snake.setType(0);

Votre serpent est en fait un python (et non un terrier). Cette fonctionnalité est appelée sécurité de type et c'est en fait la raison pour laquelle vous êtes un exemple de code

var temp = new Politician { MostNotableGrievance = 1 };

ne compilera pas. MostNotableGrievance est un MostNotableGrievance et non un entier. Avec les énumérations, vous pouvez exprimer cela dans le système de type. Et c'est pourquoi Martin Fowler et moi pensons que les dénombrements sont excellents.

écharpe
la source