Typcode durch Klasse ersetzen (aus Refactoring [Fowler])

9

Diese Strategie beinhaltet das Ersetzen von Folgendem:

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

Mit:

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

Warum genau ist dies der Aufzählung des Typs vorzuziehen?

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

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

Dem Typ ist kein Verhalten zugeordnet, und wenn dies der Fall wäre, würden Sie sowieso einen anderen Refactoring-Typ verwenden, z. B. "Typcode durch Unterklassen ersetzen" + "Bedingt durch Polymorphismus ersetzen".

Der Autor erklärt jedoch, warum er diese Methode missbilligt (in Java?):

Numerische Typcodes oder Aufzählungen sind ein häufiges Merkmal von C-basierten Sprachen. Mit symbolischen Namen können sie gut lesbar sein. Das Problem ist, dass der symbolische Name nur ein Alias ​​ist. Der Compiler sieht immer noch die zugrunde liegende Nummer. Der Compilertyp prüft anhand der Nummer 177 und nicht anhand des symbolischen Namens. Jede Methode, die den Typcode als Argument verwendet, erwartet eine Zahl, und es gibt nichts, was die Verwendung eines symbolischen Namens erzwingen könnte. Dies kann die Lesbarkeit beeinträchtigen und zu Fehlern führen.

Wenn Sie jedoch versuchen, diese Anweisung auf C # anzuwenden, scheint diese Anweisung nicht wahr zu sein: Sie akzeptiert keine Zahl, da eine Aufzählung tatsächlich als Klasse betrachtet wird. Also der folgende Code:

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

Wird nicht kompiliert. Kann dieses Refactoring in neueren Hochsprachen wie C # als unnötig angesehen werden, oder denke ich nicht über etwas nach?

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

Antworten:

6

Ich denke, Sie haben dort fast Ihre eigene Frage beantwortet.

Der zweite Code wird dem ersten vorgezogen, da er Typensicherheit bietet. Wenn Sie beim ersten Stück eine ähnliche Aufzählung von Fischen haben, können Sie so etwas sagen

MostNotableGrievance grievance = Fish.Haddock;

und der Compiler ist das egal. Wenn Fish.Haddock = 2 ist, entspricht das oben Genannte genau

MostNotableGrievance grievance = MostNotableGrievance.FlipFlopping;

aber offensichtlich nicht sofort als solche lesbar.

Der Grund, warum Aufzählungen oft nicht besser sind, liegt darin, dass Sie bei der impliziten Konvertierung von und nach int genau das gleiche Problem haben.

In C # gibt es jedoch keine solche implizite Konvertierung, sodass eine Aufzählung eine bessere Struktur ist. Sie werden selten einen der beiden ersten Ansätze sehen.

pdr
quelle
5

Wenn dem Wert kein Verhalten zugeordnet ist und Sie keine zusätzlichen Informationen daneben speichern müssen und die Sprache keine implizite Konvertierung in Ganzzahlen unterstützt, würde ich eine Aufzählung verwenden. Dafür sind sie da.

Telastyn
quelle
1

Ein weiteres Beispiel, das Ihnen helfen könnte, finden Sie in Effective Java (Punkt 30, wenn Sie es nachschlagen möchten). Es gilt auch für C #.

Angenommen, Sie verwenden int-Konstanten, um verschiedene Tiere zu modellieren, z. B. Schlangen und Hunde.

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

int DOG_TERRIER=0;
int DOG_PITBULL=1;

Angenommen, dies sind Konstanten und möglicherweise sogar in verschiedenen Klassen definiert. Dies mag Ihnen in Ordnung erscheinen und tatsächlich funktionieren. Beachten Sie jedoch diese Codezeile:

snake.setType(DOG_TERRIER);

Dies ist eindeutig ein Fehler. Der Compiler wird sich nicht beschweren und der Code wird ausgeführt. Aber deine Schlange wird nicht zu einem Hund. Das liegt daran, dass der Compiler nur Folgendes sieht:

snake.setType(0);

Ihre Schlange ist eigentlich eine Python (und kein Terrier). Diese Funktion wird als Typensicherheit bezeichnet und ist eigentlich der Grund, warum Sie Beispielcode sind

var temp = new Politician { MostNotableGrievance = 1 };

wird nicht kompiliert. MostNotableGrievance ist ein MostNotableGrievance, keine Ganzzahl. Mit Aufzählungen können Sie dies im Typensystem ausdrücken. Und deshalb finden Martin Fowler und ich Aufzählungen großartig.

Scarfridge
quelle