使用和保留枚举的最佳实践


76

我在这里看到了几个有关处理和持久保存枚举式值的最佳方法的问题/讨论(例如,持久化适用于枚举的数据如何使用NHibernate来持久化枚举),我想问一下一般共识。

尤其是:

  • 这些值应如何在代码中处理?
  • 应该如何将它们持久保存到数据库中(作为文本/作为数字)?
  • 不同解决方案的权衡是什么?

注意:我已将本问题中最初包含的解释移至答案。

Answers:


19

我同意你的大部分意见。不过,我想补充一件事,关于枚举的持久性:我不认为在构建时从DB值生成枚举是可以接受的,但是我也认为运行时检查不是一个好的解决方案。我将定义第三种方法:进行单元测试,该测试将对照数据库检查枚举的值。这样可以防止“偶然的”差异,并避免每次运行代码时都要对照数据库检查枚举的开销。


14

最初的文章对我来说不错。尽管如此,基于这些注释,似乎有关Java枚举的一些注释可能澄清不了什么。

根据定义,Java中的Enum类型是一个类,但是许多程序员往往会忘记这一点,因为它们像某些其他语言一样将其与“允许值列表”相关联。不仅如此。

因此,为避免这些switch语句,将一些代码和其他方法放在enum类中可能是合理的。几乎不需要创建单独的“枚举式真实类”。

还请考虑文档编制的要点-您是否要在数据库中记录枚举的实际含义?在反映值(您的枚举类型)的源代码中还是在某些外部文档中?我个人更喜欢源代码。

如果由于速度或其他原因要在数据库中将枚举值显示为整数,则该映射也应驻留在Java枚举中。默认情况下,您将获得字符串名称映射,对此我很满意。每个枚举值都有一个序号,但是直接使用序号作为代码和数据库之间的映射并不是很好,因为如果有人重新排序源代码中的值,则序号会改变。或在现有值之间添加其他枚举值。或去除一些价值。

(当然,如果有人在源代码中更改了枚举的名称,默认的字符串映射也会出错,但这不太可能偶然发生。而且,如果需要,可以通过进行一些运行时检查来更容易地防止这种情况的发生。并按照此处的建议检查数据库中的约束。)


1
有两种支持的方案:有人对我的文件中的枚举进行重新排序,或者有人进行一些重构(以澄清错误的初始名称选择)并破坏持久数据。我认为后者更为重要,而序数是实现数据持久性的方法。
贾斯汀2010年

7

我试图总结我的理解。如果有任何更正,请随时进行编辑。因此,它去了:

在代码中

在代码中,应使用语言的本机枚举类型(至少在Java和C#中)或使用诸如“ typesafe枚举模式”之类的方式来处理枚举。不推荐使用普通常量(整数或类似常量),因为这样会丢失类型安全性(并使您难以理解哪些值是合法输入的值,例如方法)。

两者之间的选择取决于要附加给枚举的附加功能:

  • 如果要将大量功能放入枚举(这很好,因为可以避免一直在其上进行switch()操作),则通常更适合使用一个类。
  • 另一方面,对于简单的枚举式值,语言的枚举通常更清晰。

特别是,至少在Java中,一个枚举不能从另一个类继承,因此,如果有多个具有相似行为的枚举要放入超类中,则不能使用Java的枚举。

持久枚举

要保留枚举,应为每个枚举值分配一个唯一的ID。可以是整数,也可以是短字符串。首选短字符串,因为它可以助记符(使DBA等更容易理解db中的原始数据)。

  • 在软件中,每个枚举都应具有映射功能,以在枚举(供软件内部使用)和ID值(用于持久化)之间转换。一些框架(例如(N)Hibernate)具有自动执行此操作的有限支持。否则,您必须将其放入枚举类型/类。
  • 该数据库应该(理想情况下)包含每个表的列表,其中列出了合法值。一列是ID(请参见上文),即PK。其他列可能对例如描述有意义。然后,将包含该枚举值的所有表列都可以将此“枚举表”用作FK。这样可以确保永远不会保留错误的枚举值,并允许数据库“独立”。

这种方法的一个问题是合法枚举值的列表存在于两个位置(代码和数据库)。这很难避免,因此通常被认为可以接受,但是有两种选择:

  • 只将值列表保留在数据库中,在构建时生成枚举类型。优雅,但意味着运行构建需要数据库连接,这似乎是有问题的。
  • 将代码中的值列表定义为具有权威性。在运行时(通常在启动时)检查数据库中的值,不匹配时投诉/中止。

6

在C#的代码处理中,您错过了定义deltering 0值的过程。我几乎没有失败总是将我的第一个值声明为:

public enum SomeEnum
{
    None = 0,
}

以便用作空值。由于支持类型是整数,并且整数默认为0,因此在很多地方了解枚举是否已通过编程方式进行设置非常有用。


6
我不同意。仅当您有时将变量保持未初始化状态时,这才有意义,我认为这是严重的不良做法。我经常看到这种“无”值的想法,但是我相信它只会掩盖真正的问题(未初始化的变量)。
sleske

3
它如何隐藏问题?它使它像可以为null的int一样显式。我在代码中保留未初始化的值,因为我知道CLR会将其默认设置为什么。他们仍在初始化它只是隐式的。
Quibblesome

好吧,这可能是风格问题。我坚信在声明时(或在声明之后直接在if-else中最多)完全初始化所有变量。否则,您可能会忘记初始化它们,尤其是在代码流程复杂的情况下。另请参阅c2.com/cgi/wiki?SingleStepConstructor
sleske

5

Java或C#应该始终在代码中使用枚举。免责声明:我的背景是C#。

如果该值要保留到数据库中,则应明确定义每个枚举成员的整数值,以便以后的代码更改不会意外地更改转换后的枚举值,从而改变应用程序的行为。

值应始终作为整数值保存在数据库中,以防止枚举名称重构。保留有关Wiki中每个枚举的文档,并在数据库字段中添加注释,以指向记录该类型的Wiki页面。还将XML文档添加到包含指向Wiki条目的链接的枚举类型中,以便可通过Intellisense使用。

如果使用工具生成CRUD代码,则该工具应能够定义用于列的枚举类型,以便生成的代码对象始终使用枚举成员。

如果需要将自定义逻辑应用于枚举成员,则可以选择以下选项:

  • 如果您有枚举MyEnum,则创建一个静态类MyEnumInfo,该类提供实用程序方法,以通过switch语句或任何必要的手段发现有关枚举成员的其他信息。在类名称中的枚举名称的末尾附加“ Info”可确保它们在IntelliSense中彼此相邻。
  • 用属性装饰枚举成员以指定其他参数。例如,我们开发了一个EnumDropDown控件,该控件创建一个用枚举值填充的ASP.NET下拉列表,而EnumDisplayAttribute指定用于每个成员的格式正确的显示文本。

我没有尝试过,但是在SQL Server 2005或更高版本中,理论上您可以在数据库中注册C#代码,该代码将包含枚举信息以及将值转换为枚举以在视图或其他构造中使用的能力,从而提供了一种翻译数据,以便DBA易于使用。


+1显式分配值是更改枚举时避免“腐败”的唯一方法
ashes999 2012年

3

恕我直言,至于代码部分:

您应该始终对枚举使用“枚举”类型,如果您这样做,基本上会得到很多赠品:键入安全性,封装和避免切换,某些集合(例如EnumSetEnumMap和)的支持和代码清晰。

至于持久性部分,您始终可以持久化枚举的字符串表示形式,并使用enum.valueOf(String)方法将其加载回。


原则上同意,但是至少在Java中,“枚举”受到限制,因为它不能具有超类(如上所述),因此有时“类型安全的枚举”类可能更好。
sleske

3

由于需要额外的空间并且搜索速度较慢,因此将枚举的文本值存储在数据库中比存储整数更不受欢迎。有价值的是,它比数字具有更多的含义,但是数据库用于存储,表示层用于使外观看起来更好。


1
不能保证枚举的int值会随着时间的推移而相同。
米格尔·平

另外,如果使用短字符串,则性能应相同。char(2)占用2个字节,int通常也占用2或
4。– sleske

6
@Miguel Ping:这个想法是为每个枚举显式分配一个ID(int或char)。使用枚举的内部生成的int确实非常危险。
sleske

1
如果在数据库中需要超出整数值的含义,那么我将使用一个将整数值映射到人类可读字符串的表。同样,是的,整数值以后不得更改。不过应该不成问题;基础枚举整数值应该具有的唯一相关性是它们与枚举的其他成员不同。(即:他们应该没有理由改变)。如果整数值的含义超出唯一标识的范围,则可能应使用其他数据结构。
戴夫·库西诺

3

嗯,根据我的经验,将枚举用于将选项(作为标志)传递给即时方法调用以外的任何方法,都会switch在某个时候导致-ing。

  • 如果要在整个代码中使用枚举,那么最终可能会得到不太容易维护的代码(臭名昭著的switch语句)
  • 扩展枚举是一种痛苦。您添加了一个新的枚举项,并最终遍历了所有代码以检查所有条件。
  • 使用.NET 3.5,您可以向枚举添加扩展方法,以使它们的行为更像类。但是,以这种方式添加实际功能并不容易,因为它仍然不是类(switch如果没有其他类,最终将在扩展方法中使用-es)。

因此,对于具有更多功能的枚举式实体,您应该花一些时间并将其创建为类,并牢记以下几点:

  • 为了使您的类表现得像枚举,可以强制每个派生类以Singleton实例化,也可以重写Equals以允许比较不同实例的值。
  • 如果您的类是枚举类,则意味着它不应该包含可序列化的状态-仅应从其类型(如您所说的一种“ ID”)进行反序列化。
  • 持久性逻辑应仅限于基类,否则扩展您的“枚举”将是一场噩梦。如果您选择了Singleton模式,则需要确保正确反序列化为Singleton实例。

3

每次您在代码中使用“幻数”找到自己时,都会将其更改为枚举。除了节省时间(由于错误会在魔术消失时自动消除魔术……)之外,还可以节省您的视力和内存(有意义的枚举使代码更具可读性和自说明性),因为您猜到了什么—您很可能是维护和开发人员你自己的代码


1

我知道这是一个古老的论坛,如果数据库可能有其他直接集成的东西怎么办?例如,当结果数据库是代码的SOLE目的时。然后,您将在每次集成时定义枚举。最好将它们放在数据库中。否则,我同意原帖。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.