Zamień kod typu na klasę (z refaktoryzacji [Fowler])

9

Ta strategia polega na zastąpieniu takich:

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

Z:

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

Dlaczego właśnie tak jest preferowane, aby typ był wyliczeniem, tak jak poniżej:

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

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

Z typem nie wiąże się żadne zachowanie, a jeśli tak, to i tak używałbyś innego rodzaju refaktoryzacji, na przykład „Zamień kod typu na podklasy” + „Zamień warunkowo na polimorfizm”.

Jednak autor wyjaśnia, dlaczego marszczy brwi wobec tej metody (w Javie?):

Kody typów numerycznych lub wyliczenia są wspólną cechą języków opartych na języku C. Dzięki symbolicznym nazwom mogą być dość czytelne. Problem polega na tym, że nazwa symboliczna jest tylko pseudonimem; kompilator nadal widzi liczbę bazową. Typ kompilatora sprawdza, używając liczby 177, a nie nazwy symbolicznej. Każda metoda, która przyjmuje kod typu jako argument, oczekuje liczby i nic nie zmusza do użycia nazwy symbolicznej. Może to zmniejszyć czytelność i być źródłem błędów.

Ale podczas próby zastosowania tej instrukcji do C #, ta instrukcja wydaje się nieprawdziwa: nie przyjmie liczby, ponieważ wyliczenie jest faktycznie uważane za klasę. Więc następujący kod:

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

Nie będzie się kompilować. Czy zatem refaktoryzację można uznać za niepotrzebną w nowszych językach wysokiego poziomu, takich jak C #, czy też nie rozważam czegoś?

Merritt
źródło
var temp = new Politician { MostNotableGrievance = MostNotableGrievance.Embezzlement };
Robert Harvey,

Odpowiedzi:

6

Myślę, że prawie odpowiedziałeś na własne pytanie.

Drugi fragment kodu jest lepszy niż pierwszy, ponieważ zapewnia bezpieczeństwo typu. W pierwszej części, jeśli masz podobne wyliczenie Ryb, możesz powiedzieć coś takiego

MostNotableGrievance grievance = Fish.Haddock;

i kompilator nie będzie się tym przejmował. Jeśli Fish.Haddock = 2, powyższe będzie dokładnie równoważne z

MostNotableGrievance grievance = MostNotableGrievance.FlipFlopping;

ale oczywiście nie od razu czytelne jako takie.

Powodem, dla którego wyliczenia często nie są lepsze, jest to, że niejawna konwersja do i z int pozostawia ci dokładnie ten sam problem.

Ale w języku C # nie ma takiej niejawnej konwersji, więc wyliczenie jest lepszą strukturą do użycia. Rzadko zobaczysz jedno z dwóch pierwszych zastosowanych podejść.

pdr
źródło
5

Jeśli nie ma żadnego zachowania związanego z wartością i nie ma żadnych dodatkowych informacji, które należy przechowywać obok niej, a język nie obsługuje niejawnej konwersji na liczby całkowite, użyłbym wyliczenia; po to są.

Telastyn
źródło
1

Kolejny przykład, który może ci pomóc, znajdziesz w Effective Java (pozycja 30, jeśli chcesz to sprawdzić). Dotyczy to również C #.

Załóżmy, że używasz stałych int do modelowania różnych zwierząt, na przykład węży i ​​psów.

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

int DOG_TERRIER=0;
int DOG_PITBULL=1;

Załóżmy, że są to stałe, a może nawet zdefiniowane w różnych klasach. Może ci się to wydawać w porządku i może się naprawdę udać. Ale rozważ ten wiersz kodu:

snake.setType(DOG_TERRIER);

Oczywiście jest to błąd. Kompilator nie będzie narzekał i kod zostanie wykonany. Ale twój wąż nie zmieni się w psa. Jest tak, ponieważ kompilator widzi tylko:

snake.setType(0);

Twój wąż jest właściwie pytonem (a nie terierem). Ta funkcja nazywa się bezpieczeństwem typu i jest właściwie powodem, dla którego jesteś przykładowym kodem

var temp = new Politician { MostNotableGrievance = 1 };

nie skompiluje się. MostNotableGrievance to MostNotableGrievance, a nie liczba całkowita. Za pomocą wyliczeń możesz wyrazić to w systemie pisma. I dlatego myślę, że Martin Fowler i wyliczenia są świetne.

szalik
źródło