Подтвердить что ты не робот

Когда использовать перечисления, а когда заменить их классом статическими членами?

Недавно мне показалось, что следующее (выборка) перечисления...

enum Color
{
    Red,
    Green,
    Yellow,
    Blue
}

... может быть заменен, казалось бы, более безопасным по типу классам:

class Color
{
    private Color() { }

    public static readonly Color   Red      = new Color();
    public static readonly Color   Green    = new Color();
    public static readonly Color   Yellow   = new Color();
    public static readonly Color   Blue     = new Color();
}

С "безопасным типом" я имею в виду, что следующий оператор будет работать, если Color был перечислением, но не если Color были указанным выше классом:

var nonsenseColor = (Color)17;    // works if Color is an enum

Два вопроса:

1) Существует ли широко распространенное имя этого шаблона (заменяя перечислимое число классом типа)?

2) В каких случаях следует использовать перечисления и когда класс будет более подходящим?

4b9b3361

Ответ 1

Перечисления отлично подходят для информации о состоянии легкого состояния. Например, ваш цветной перечислитель (исключая синий) был бы хорош для запроса состояния светофора. Истинный цвет вместе со всей концепцией цвета и всего его багажа (альфа, цветовое пространство и т.д.) Не имеет значения, только в каком состоянии находится свет. Кроме того, изменение вашего перечисления немного, чтобы представить состояние светофора

[Flags()]
public enum LightColors
{
    unknown = 0,
    red = 1,
    yellow = 2,
    green = 4,
    green_arrow = 8
}

Текущее состояние освещения может быть установлено как:

LightColors c = LightColors.red | LightColors.green_arrow;

И запрашивается как:

if ((c & LightColors.red) == LightColors.red)
{
    //Don't drive
}
else if ((c & LightColors.green_arrow) == LightColors.green_arrow)
{
    //Turn
}

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

Однако статические члены класса замечательны для обычно используемых объектов. Элементы System.Drawing.Color являются прекрасными примерами, поскольку они представляют собой цвета с известным именем, которые имеют неясные конструкторы (если только вы не знаете свои шестнадцатеричные цвета). Если бы они были реализованы как перечисления, вам нужно было бы делать что-то подобное каждый раз, когда вы хотели использовать значение как цвет:

colors c = colors.red;
switch (c)
{
    case colors.red:
        return System.Drawing.Color.FromArgb(255, 0, 0);
        break;
    case colors.green:
        return System.Drawing.Color.FromArgb(0,255,0);
        break;
}

Итак, если у вас есть перечисление и вы обнаружите, что вы постоянно делаете переключатель /case/if/else/what, чтобы получить объект, вы можете использовать статические члены класса. Если вы только запрашиваете состояние чего-то, я буду придерживаться перечислений. Кроме того, если вам нужно передавать данные в небезопасном режиме, перечисления, вероятно, выживут лучше, чем сериализованная версия вашего объекта.

Edit: @stakx, я думаю, что вы наткнулись на что-то важное, тоже в ответ на пост @Anton, и это сложность или, что более важно, кому это сложно?

С точки зрения потребителя, я бы очень хотел, чтобы статические члены System.Drawing.Color стали писать все это. С точки зрения производителя, однако, было бы больно писать все это. Поэтому, если другие люди будут использовать ваш код, вы можете сэкономить им много хлопот, используя статических членов класса, даже если вам понадобится в 10 раз больше времени для написания/тестирования/отладки. Тем не менее, если только вам, вам может быть проще просто использовать перечисления и преобразовать/преобразовать по мере необходимости.

Ответ 2

На самом деле я очень много борется с чем-то вроде этого.

Он был основан на примере который был опубликован Джоном B в Статья в блоге Jon Skeet "Расширенные перечисления в С#" .

То, что я не получал, чтобы работать должным образом без LOT уродства, заключалось в расширении перечисления путем получения базового класса перечисления и добавления дополнительных статических полей типа вывода.

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

Я имею в виду, что вам будет трудно создать переменную DerivedEnumClass1 и нужно сохранить значение из нее, которая фактически имеет тип BaseEnumClass1 в этой переменной без обходных путей.

Другой проблемой была Xml Serialization, которую мы используем много. Я обошел это, используя два общих класса в качестве типов данных для переменных перечисления на наших бизнес-объектах: один, который представлял одно значение перечисления, и тот, который представлял набор значений перечисления, которые я использовал для имитации поведения перечислений флагов. Они обрабатывали сериализацию и десериализацию xml фактических значений, которые были сохранены в свойствах, которые они представляли. Перегрузкой пары были все, что было необходимо для воссоздания поведения битфлага таким образом.

Вот некоторые из основных проблем, с которыми я столкнулся.

В общем, я не очень доволен конечным результатом, какой-то вонючий материал, но сейчас он делает эту работу.

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

У меня нет кода здесь, и я не смогу добраться до него на выходные, иначе я мог бы показать, куда я пошел. Не очень красиво, хотя...: o

Ответ 3

Некоторые вещи, которые я нашел в то же время, если кому-то еще интересно:

  • switch блоки не будут работать с enum-as-class.

  • Пользователь empi упомянул сходство приведенного выше примера перечисления как класса с перечислениями Java. Кажется, что в мире Java существует признанный шаблон, называемый Резервирование имен; по-видимому, эта картина восходит к книге Джошуа Блоха "Эффективная Java".

Ответ 4

Я бы использовал константы класса, когда мне пришлось взаимодействовать с каким-то унаследованным кодом, который хочет получить некоторые нечеткие значения флагов бит, которые могли бы быть объединены. В этом случае перечисление может выглядеть как

public enum Legacy : ushort
{
  SomeFlag1 = 0x0001,
  SomeFlag2 = 0x0002,
  // etc...
}

Затем сортировка в P/Invoke менее читаема из-за кастинга, необходимого для перевода перечисления в соответствующее значение.

Если бы это была переменная класса const, то приведение не требовалось бы.

Ответ 5

Оба подхода действительны. Вы должны выбрать для каждого случая.

Я могу добавить, что перечисления поддерживают битовые операции как флаги (там даже атрибут [Flags] поддерживает эту семантику и создает целые строки из перечислений).

Там очень похожий рефакторинг под названием "Замена перечислений" шаблоном стратегии. Ну, в вашем случае это не совсем полно, так как ваш цвет пассивен и не действует как стратегия. Однако почему бы не подумать об этом как о специальном случае?

Ответ 6

Yup, оригинальная версия "Эффективной Java" Джошуа Блоха, выпущенная до Java 1.5 и встроенная поддержка перечислений на Java, продемонстрировала шаблон Typesafe Enum. К сожалению, последний выпуск книги нацелен на Java > 1.5, поэтому вместо этого он использует перечисления Java.

Во-вторых, вы не можете отбрасывать из int в enum в Java.

Color nonsenseColor = (Color)17; // compile-error

Я не могу говорить на других языках.