类型安全的枚举用于限定值

huangapple go评论82阅读模式
英文:

Type-safe enums for limited values

问题

在Go语言中,如何实现对有限值范围的类型安全枚举呢?

例如,假设我想要用简单的尺寸(Size)和颜色(Color)来模拟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}

但是,如何确保不会创建具有未定义尺寸/颜色的TShirt呢?

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

为了确保不会创建具有未定义尺寸/颜色的TShirt,你可以使用Go的类型系统和常量来实现类型安全的枚举。在上面的代码中,SizeColor类型都是基于string的自定义类型,并使用常量来定义有效的取值范围。

如果你尝试创建具有未定义尺寸/颜色的TShirt,Go编译器将会报错,因为常量定义了有效的取值范围,而不在范围内的值将被认为是错误的。

这样,你就可以确保只能创建具有预定义尺寸/颜色的TShirt实例,而不会出现不支持的值。

英文:

How does one achieve type-safe enums over a limited range of values in Go?

For example, suppose I wanted to model t-shirts with a simple Size and Color, both with limited possible values, ensuring that I could not create an instance with unsupported values.

I could declare the type Size and Color based on string, define enums for the valid values, and a TShirt type that uses them:

<!-- language: lang-go -->
type Size string
const (
Small Size = "sm"
Medium = "md"
Large = "lg"
// ...
)

type Color string
const (
  Red   Color = &quot;r&quot;
  Green       = &quot;g&quot;
  Blue        = &quot;b&quot;
  // ...
)

type TShirt struct {
  Size  Size
  Color Color
}

var mediumBlueShirt = TShirt{Medium, Blue}

But how could I ensure that no TShirt with undefined size/color is created?

<!-- language: lang-go -->
var doNotWant = TShirt{"OutrageouslyLarge", "ImpossibleColor"}

答案1

得分: 4

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

// 导出的接口
type Size interface {
   // 未导出的方法
   isSize()
}

// 未导出的类型
type size struct {
   sz string
}

func (s size) String() string {return s.sz}
// 实现导出的接口
func (size) isSize() {}

var (
  Small Size = size{"small"}
  Large Size = size{"large"}
)

这样你只能使用预定义的值。

通常情况下,这样做并不值得麻烦。考虑对这样的枚举进行验证,以确保其值在已知的值集合中。

英文:

There is no direct support for limited set enum, however you can use the type system to achieve that:

// 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{&quot;small&quot;}
  Large Size = size{&quot;large&quot;}
)

This way you can only use predeclared values.

This is usually not worth the trouble. Consider validating such enums against a known valueset.

答案2

得分: 2

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

type Size int
const (
    Small Size = iota
    Medium
    Large
    maxSize // 始终将其放在最后
)

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放在常量块的末尾,你的Valid函数将始终起作用,即使添加或删除枚举值。

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

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

英文:

If you make your enums based on integer, you can create a pseudo value which can be used to check validity:

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

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

func (s Size) String() string {
    return sizeToString
展开收缩
} func (s Size) Valid() bool { return s &gt; 0 &amp;&amp; s &lt; maxSize }

If you keep the maxSize at the end of the const block your Valid function will always work, even after adding or removing enum values.

You can do the same for your second enum type and your composite type can also define a Valid functions which returns true if both enums are valid as well.

When I use this pattern I always include a unit test to make sure that every enum value has a string translation so I don't forget. You can do this by making a for loop from 0 to maxSize and check that you never get an empty string back from sizeToString

huangapple
  • 本文由 发表于 2021年11月12日 02:53:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/69933473.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定