"Enum as immutable rich-object": 这是一种反模式吗?

15

我经常看到并使用带有属性的枚举来完成一些基本任务,例如提供显示名称或描述:

public enum Movement {
    [DisplayName("Turned Right")]
    TurnedRight,
    [DisplayName("Turned Left")]
    [Description("Execute 90 degree turn to the left")]
    TurnedLeft,
    // ...
}

同时还拥有了一组扩展方法来支持属性:

public static string GetDisplayName(this Movement movement) { ... }
public static Movement GetNextTurn(this Movement movement, ...)  { ... }

按照这种模式,可以针对字段应用其他现有或自定义属性以执行其他操作。这就像枚举类型既可以作为简单的枚举值类型进行工作,又可以作为更丰富的不可变值对象,具有许多字段:

public class Movement
{
    public int Value { get; set; } // i.e. the type backing the enum
    public string DisplayName { get; set; }
    public string Description { get; set; }
    public Movement GetNextTurn(...) { ... }
    // ...
}

这样,它可以在序列化期间作为一个简单的字段“传递”,快速比较等,但行为可以被“内部化”(类似于面向对象编程)。

话虽如此,我认识到这可能被视为反模式。与此同时,我认为其中一部分很有用,以至于“反”可能太严格了。


4
您在问这种模式是否适用于非枚举情境,这种模式很适合将元数据与枚举值关联起来,但作为类的替代品就不是很好了。 - Kirk Woll
这可能被视为语言的“滥用”;就像在不必要的情况下始终使用dynamic一样。 - Kit
1
虽然与主题不完全相关(因此有评论),但我对编码要在属性中显示文本,或者在实体类附近显示文本持有顾虑。首先,这严重违反了“关注点分离”原则;第二,没有明显的本地化方式。唉,微软似乎鼓励这种做法,所以也许我就是自作多情了。 - Daniel Pratt
1
@KirkWoll 我指的是 'DisplayName' 属性。 - Daniel Pratt
这与C#无关,但Java枚举本质上是类,你可以像类一样给它们属性和方法,但它们的功能就像枚举。我喜欢这种设计,我认为它非常有用,但我也能看到它被滥用的可能性。我见过的两个最好的例子(在我看来)是Minecraft中使用枚举来表示物品的材料和最大使用次数,以及用于性别的.hisHer()方法和其他返回特定于性别的字符串的方法。 - Brandon Buck
显示剩余7条评论
2个回答

7
我认为在C#中这是一种不良的模式,因为声明和访问属性的语言支持非常有限;它们并不意味着存储很多数据。声明具有非平凡值的属性很麻烦,获取属性值也很困难。一旦你想将某些有趣的东西与枚举关联起来(例如计算枚举上某些内容的方法或包含非基元数据类型的属性),你就需要将其重构为类或将其他内容放入带外位置。
使用一些静态实例创建不可变类并保存相同信息并不比枚举更加困难,而且在我看来更符合惯用法。

声明枚举和不可变的丰富对象并不难,实现丰富枚举确实有些麻烦,但是在使用方面呢?尽管你提到的习惯用法很有道理。 - Kit
写惯用代码的好处在于,你总是希望编写的代码能够与所在的语言/框架相协调。 - FMM

5
我认为这是一种反模式。原因如下。让我们看看您现有的枚举(为了简洁起见,去掉了属性):
public enum Movement
{
    TurnedRight,
    TurnedLeft,
    Stopped,
    Started
}

现在,假设需要的内容更加精确一些;比如,需要改变标题和/或速度,将你的“伪类”中的一个“字段”变成两个:

public sealed class Movement
{
    double HeadingDelta { get; private set; }
    double VelocityDelta { get; private set; }
    // other methods here
}

所以,您有一个已编码的枚举,现在必须将其转换为不可变类,因为您现在正在跟踪两个正交(但仍然是不可变的)属性,这些属性确实应该在同一接口中归属在一起。您针对“富枚举”编写的任何代码现在都必须进行大量重构;而如果您从类开始,您可能需要做更少的工作。
您必须询问如何随时间维护代码,以及富枚举是否比类更易于维护。我打赌它不会更易于维护。另外,正如mquander指出的那样,在C#中,基于类的方法更加惯用。
还有其他要考虑的事情。如果对象是不可变的,并且是struct而不是class,则可以获得与枚举相同的按值传递语义,并且在序列化大小和对象运行时大小方面几乎没有差异。

我之前也遇到过这个问题,完全同意。 - tacos_tacos_tacos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接