【问题标题】:Type-safe enums for limited values [duplicate]有限值的类型安全枚举
【发布时间】:2021-11-11 18:53:39
【问题描述】:

如何在 Go 中的有限值范围内实现类型安全的枚举?

例如,假设我想用简单的尺寸和颜色为 T 恤建模,两者都具有有限的可能值,以确保我无法创建具有不受支持的值的实例。

我可以基于string 声明SizeColor 类型,为有效值定义枚举,以及使用它们的TShirt 类型:

type Size string
const (
  Small  Size = "sm"
  Medium      = "md"
  Large       = "lg"
  // ...
)

type Color string
const (
  Red   Color = "r"
  Green       = "g"
  Blue        = "b"
  // ...
)

type TShirt struct {
  Size  Size
  Color Color
}

var mediumBlueShirt = TShirt{Medium, Blue}

但我如何确保不会创建尺寸/颜色未定义的 T 恤?

var doNotWant = TShirt{"OutrageouslyLarge", "ImpossibleColor"}

【问题讨论】:

  • 没有内置语言支持。但是,您可以取消导出字段并实现在设置字段之前验证输入的 setter 方法。虽然我没有考虑这么多,但可能有更好的方法被广泛接受。
  • 你可能已经看过这个stackoverflow.com/questions/14426366/…。与建议的副本的区别正是类型安全。将具有预定义基础类型的枚举声明为 intstring 存在可分配性问题。可以嵌入接口枚举以规避类型约束。

标签: go


【解决方案1】:

如果你的枚举是基于整数的,你可以创建一个伪值来检查有效性:

type Size int
const (
    Small Size = iota
    Medium
    Large
    maxSize // Always keep this at the end
)

var sizeToString = map[Size]string{
    Small: "sm",
    Medium: "md",
    Large: "lg",
}

func (s Size) String() string {
    return sizeToString[s]
}

func (s Size) Valid() bool {
    return s > 0 && s < maxSize
}

如果您将 maxSize 保留在 const 块的末尾,您的 Valid 函数将始终有效,即使在添加或删除枚举值之后也是如此。

你可以对你的第二个枚举类型做同样的事情,你的复合类型也可以定义一个 Valid 函数,如果两个枚举都有效,则返回 true。

当我使用这种模式时,我总是包含一个单元测试来确保每个枚举值都有一个字符串翻译,这样我就不会忘记。您可以通过从 0 到 maxSize 进行 for 循环来做到这一点,并检查您是否永远不会从 sizeToString 得到空字符串

【讨论】:

    【解决方案2】:

    没有对有限集合枚举的直接支持,但是您可以使用类型系统来实现:

    // Exported interface 
    type Size interface {
       // Unexported method
       isSize()
    }
    
    // unexported type
    type size struct {
       sz string
    }
    
    func (s size) String() string {return s.sz}
    // Implement the exported interface
    func (size) isSize() {}
    
    var (
      Small Size = size{"small"}
      Large Size = size{"large"}
    )
    

    这样你只能使用预先声明的值。

    这通常不值得麻烦。考虑根据已知值集验证此类枚举。

    【讨论】:

    • 不要太迂腐,但是,如果我导入你的包并将 exported 接口嵌入到我的假类型中,它将默认实现它,然后我可以传递我的假类型为大小。只要该包中没有实现它的任何其他导出类型,取消导出接口就可以避免这种情况。但我同意你最后的观点,这不值得麻烦。
    • @mkopriva,关于嵌入接口的要点。您将需要一个导出的类型来使用枚举在其他包中定义其他类型或函数。我想知道是否有办法破解它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-19
    • 2020-12-30
    • 2010-09-07
    相关资源
    最近更新 更多