Рекомендации по использованию и сохранению перечислений

Я видел несколько вопросов/дискуссий о том, как лучше всего обрабатывать и сохранять значения, подобные перечислению (например, Сохраняющиеся данные, подходящие для перечислений, Как сохранить перечисление с помощью NHibernate), и я хотел бы спросить, что такое общий консенс.

В частности:

  • Как эти значения должны обрабатываться в коде?
  • Как они должны сохраняться в базе данных (в виде текста/в виде числа)?
  • Каковы компромиссы между различными решениями?

Примечание. Я перевел объяснения, первоначально включенные в этот вопрос, на ответ.

Ответ 1

Я попытался подытожить свое понимание. Не стесняйтесь редактировать это, если у вас есть какие-либо исправления. Итак, вот оно:

В коде

В коде перечисления должны обрабатываться либо с использованием языка родного типа перечисления (по крайней мере, на Java и С#), либо с использованием чего-то типа "typeafe enum pattern" . Использование простых констант (целое или подобное) не рекомендуется, так как вы теряете безопасность типа (и затрудняетесь понять, какие значения являются законными вводами, например, способом).

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

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

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

Сохраняющиеся перечисления

Для сохранения перечислений для каждого значения перечисления должен быть присвоен уникальный идентификатор. Это может быть либо целое число, либо короткая строка. Предпочтительна короткая строка, поскольку она может быть мнемонической (упрощает администрирование баз данных и т.д. Для понимания исходных данных в db).

  • В программном обеспечении каждое перечисление должно иметь функции отображения для преобразования между перечислением (для использования внутри программного обеспечения) и значением идентификатора (для сохранения). Некоторые рамки (например, (N) Hibernate) имеют ограниченный suppport для этого автоматически. В противном случае вы должны поместить его в тип/класс перечисления.
  • База данных должна (в идеале) содержать таблицу для каждого перечисления, в которой перечислены правовые значения. Один столбец будет ID (см. Выше), который является PK. Дополнительные столбцы могут иметь смысл для, например, описание. Все столбцы таблицы, которые будут содержать значения из этого перечисления, могут затем использовать эту таблицу "enum" как FK. Это гарантирует, что некорректные значения перечисления никогда не будут сохраняться, и позволяет БД "стоять самостоятельно".

Одна из проблем с этим подходом заключается в том, что список юридических значений enum существует в двух местах (код и база данных). Это трудно избежать и поэтому часто считается приемлемым, но есть две альтернативы:

  • Сохранять только список значений в БД, генерировать тип перечисления во время сборки. Элегантный, но означает, что для запуска сборки требуется соединение с БД, что кажется проблематичным.
  • Определите список значений в коде, который будет авторитетным. Проверяйте значения в БД во время выполнения (обычно при запуске), жалуйтесь/прерывайте несоответствие.

Ответ 2

Я согласен с тем, что вы говорите. Одна вещь, которую я хотел бы добавить, однако, в отношении сохранения перечислений: я не верю, что генерация переписей во время сборки из значений DB приемлема, но я также считаю, что проверка времени выполнения не является хорошим решением, Я бы определил третье средство: есть unit test, который будет проверять значения перечисления на базу данных. Это предотвращает "случайную" расхождение и позволяет избежать накладных расходов на проверку перечислений по базе данных каждый раз, когда выполняется код.

Ответ 3

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

Тип перечисления в Java - это класс по определению, но многие программисты склонны забывать об этом, потому что они скорее связывают его с "списком допустимых значений", как на некоторых других языках. Это более того.

Итак, чтобы избежать этих операторов switch, было бы разумно поместить некоторый код и дополнительные методы в класс enum. Почти нет необходимости создавать отдельный "enum-like real class".

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

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

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

Ответ 4

Java или С# всегда должны использовать перечисления в коде. Отказ от ответственности: мой фон - С#.

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

Значения всегда должны сохраняться в базе данных как целостные значения для защиты от рефакторинга имен перечислений. Храните документацию по каждому перечислению в вики и добавьте комментарий в поле базы данных, указывающее на страницу вики, документирующую тип. Также добавьте XML-документацию в тип перечисления, содержащий ссылку на запись в вики, чтобы она была доступна через Intellisense.

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

Если пользовательская логика должна быть применена для элемента перечисления, у вас есть несколько вариантов:

  • Если у вас есть enum MyEnum, создайте статический класс MyEnumInfo, который предлагает методы утилиты для обнаружения дополнительной информации о члене перечисления, инструкциях переключателя или любых необходимых средствах. Добавление "Info" в конец имени перечисления в имени класса гарантирует, что они будут рядом друг с другом в IntelliSense.
  • Украсьте элементы перечисления атрибутами, чтобы указать дополнительные параметры. Например, мы разработали элемент управления EnumDropDown, который создает раскрывающийся список ASP.NET, заполненный значениями перечисления, а атрибут EnumDisplayAttribute указывает красиво отформатированный текст отображения для каждого члена.

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

Ответ 5

В обработчике кода для С# вы пропустили определение декантации значения 0. Я почти непременно объявляю свое первое значение как:

public enum SomeEnum
{
    None = 0,
}

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

Ответ 6

Сохранение текстового значения перечисления в базе данных менее предпочтительно для хранения целого числа из-за дополнительного пространства и медленного поиска. Он ценен тем, что он имеет больше смысла, чем число, однако база данных предназначена для хранения, а слой представления - для того, чтобы все выглядело хорошо.

Ответ 7

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

  • Если вы собираетесь использовать перечисление по всему вашему коду, вы можете получить код, который не так прост в обслуживании (печально известный оператор switch)
  • Расширение перечислений - это боль. Вы добавляете новый элемент перечисления и в конечном итоге просматриваете весь свой код, чтобы проверить все условия.
  • С .NET 3.5 вы можете добавить методы расширений в перечисления, чтобы заставить их вести себя скорее как классы. Однако добавление реальной функциональности таким образом не так просто, так как оно все еще не является классом (вы в конечном итоге используете switch -es в своих методах расширения, если не в другом месте.

Итак, для объекта с перечислением, обладающего немного большей функциональностью, вы должны потратить некоторое время и создать его как класс, имея в виду несколько вещей:

  • Чтобы ваш класс вел себя как перечисление, вы можете либо заставить каждый производный класс создавать экземпляр в качестве Singleton, либо переопределить Equals для сравнения значений разных экземпляров.
  • Если ваш класс переименован, он должен означать, что он не должен содержать сериализуемое состояние. - десериализация должна быть возможна только из его типа (своего рода "идентификатор", как вы сказали).
  • Логика сдерживания должна ограничиваться только базовым классом, в противном случае расширение вашего "перечисления" было бы кошмаром. В случае, если вы отправились на шаблон Singleton, вам необходимо обеспечить надлежащую десериализацию в экземплярах singleton.

Ответ 8

Каждый раз, когда вы находите свое "я", используя "магические числа" при смене кода на перечисления. Помимо экономии времени (поскольку волшебство исчезнет, ​​когда появятся ошибки...), это спасет ваши глаза и память (значимые перечисления делают код более понятным и самодокументируемым), так как догадываетесь, что - вы, скорее всего, человек, который должен поддерживать и развивать ваш собственный код

Ответ 9

Imho, что касается части кода:

Вы должны всегда использовать тип "enum" для своих перечислений, в основном вы получаете много халявы, если вы это делаете: введите безопасность, инкапсуляцию и отказ от отказа, поддержку некоторых коллекций, таких как EnumSet и EnumMap и четкость кода.

как и для части сохранения, вы всегда можете сохранить строковое представление перечисления и загрузить его с помощью метода enum.valueOf(String).

Ответ 10

Я знаю, что это старый форум, что делать, если в базе данных могут быть другие вещи, непосредственно связанные с ним? Например. когда результирующая БД является целью кода SOLE. Затем вы будете определять перечисления при каждой интеграции. Лучше тогда иметь их в БД. В противном случае я согласен с оригинальным сообщением.