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

时间:2011-10-24 20:41:21

标签: c# .net extension-methods design-patterns anti-patterns

我经常看到并使用带有附加属性的枚举来做一些基本的事情,比如提供显示名称或描述:

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(...) { ... }
    // ...
}

通过这种方式,它可以在序列化过程中作为一个简单的字段“移动”,快速比较等等,但行为可以“内化”(ala OOP)。

那就是说,我认识到这可能被视为一种反模式。与此同时,我的一部分认为这很有用, anti 可能过于严格。

2 个答案:

答案 0 :(得分:7)

我认为这在C#中是一个糟糕的模式,因为声明和访问属性的语言支持是如此残缺;它们并不意味着存储大量数据。用非平凡值声明属性是一件痛苦的事,获取属性的值很痛苦。一旦你想要与你的枚举相关的远程有趣的东西(比如在枚举上计算某些东西的方法,或者包含非原始数据类型的属性),你需要将它重构为一个类或者把另一个东西放在一个类中。一些带外的地方。

使用保存相同信息的静态实例制作不可变类并不是更难,在我看来,它更具惯用性。

答案 1 :(得分: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,那么您将获得相同的值传递语义,并且序列化大小和对象的运行时大小可以忽略不计枚举。