Заменить код типа классом (из рефакторинга [Фаулера])

9

Эта стратегия предполагает замену подобных:

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

С:

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

Почему именно это предпочтительнее, чем делать тип перечислением, например так:

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

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

С типом не связано поведение, и если бы это было, вы бы в любом случае использовали рефакторинг другого типа, например, «Заменить код типа на подклассы» + «Заменить условный на полиморфизм».

Тем не менее, автор объясняет, почему он не одобряет этот метод (на Java?):

Числовые коды типов или перечисления являются общей чертой языков Си. С символическими именами они могут быть вполне читабельными. Проблема в том, что символическое имя является только псевдонимом; Компилятор по-прежнему видит основной номер. Тип компилятора проверяет номер 177, а не символическое имя. Любой метод, который принимает код типа в качестве аргумента, ожидает число, и нет ничего, чтобы принудительно использовать символическое имя. Это может снизить читабельность и стать источником ошибок.

Но при попытке применить этот оператор к C # этот оператор не выглядит верным: он не будет принимать число, потому что перечисление фактически считается классом. Итак, следующий код:

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

Не будет компилироваться. Так можно ли считать этот рефакторинг ненужным в новых языках высокого уровня, таких как C #, или я что-то не рассматриваю?

Мерритт
источник
var temp = new Politician { MostNotableGrievance = MostNotableGrievance.Embezzlement };
Роберт Харви

Ответы:

6

Я думаю, что вы почти ответили на свой вопрос там.

Второй кусок кода предпочтительнее первого, потому что он обеспечивает безопасность типов. В первой части, если у вас есть аналогичное перечисление рыбы, вы можете сказать что-то вроде

MostNotableGrievance grievance = Fish.Haddock;

и компилятору все равно. Если Fish.Haddock = 2, то приведенное выше будет в точности эквивалентно

MostNotableGrievance grievance = MostNotableGrievance.FlipFlopping;

но, очевидно, не сразу читается как таковой.

Причины перечисления часто не лучше, потому что неявное преобразование в и из int оставляет вас с той же самой проблемой.

Но в C # такого неявного преобразования нет, поэтому enum - лучшая структура для использования. Вы редко увидите один из первых двух подходов.

прецизионный самописец
источник
5

Если с этим значением нет поведения, и нет никакой дополнительной информации, которую нужно хранить рядом с ним, а язык не поддерживает неявное преобразование в целые числа, я бы использовал enum; вот для чего они здесь.

Telastyn
источник
1

Другой пример, который может вам помочь, можно найти в Effective Java (пункт 30, если вы хотите посмотреть его). Это верно и для C #.

Предположим, вы используете int-константы для моделирования различных животных, например, змей и собак.

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

int DOG_TERRIER=0;
int DOG_PITBULL=1;

Предположим, что они являются константами и, возможно, даже определены в разных классах. Это может показаться вам хорошим, и это может сработать. Но рассмотрим эту строку кода:

snake.setType(DOG_TERRIER);

Понятно, что это ошибка. Компилятор не будет жаловаться, и код будет выполнен. Но твоя змея не превратится в собаку. Это потому, что компилятор видит только:

snake.setType(0);

Ваша змея на самом деле питон (а не терьер). Эта функция называется безопасностью типов и фактически является причиной того, почему вы используете пример кода.

var temp = new Politician { MostNotableGrievance = 1 };

не скомпилируется. MostNotableGrievance - это MostNotableGrievance, а не целое число. С помощью перечислений вы можете выразить это в системе типов. И именно поэтому мы с Мартином Фаулером считаем, что перечисления великолепны.

scarfridge
источник