在 Smalltalk 中定义枚举类型的惯用方法是什么?

What is the idiomatic way to define an enum type in Smalltalk?

就像在 Java、C# 等中一样。我如何在 Smalltalk 中定义类似 enum Direction { input, output } 的东西?

简单的方法

最简单的方法是使用 class-side 方法 returning 符号或其他基本对象(例如整数)。

所以你可以这样写你的例子:

Direction class>>input
    ^ #input

Direction class>>output
    ^ #output

以及用法:

Direction input

主要缺点是:

  • 发生在 return 相同值上的任何其他 "enum" 将等于此值,即使枚举不同(您可以 return 例如 ^ self name + '::input'
  • 在调试过程中,您会看到对象的实际值,这对于 number-based 枚举来说尤其难看(呃...这个 7 是什么意思?

对象方法

更好的方法是创建您自己的枚举对象和它的 return 个实例。

这样的对象应该:

  • 覆盖 =hash,因此您可以按您的值比较它们并使用枚举作为散列集合(字典)中的键
  • 存储实际的唯一值表示
  • 有自定义 printOn: 方法来简化调试

它可能看起来像这样

Object subclass: #DirectionEnum
    slots: { #value }
    classVariables: {  }
    category: 'DirectionEnum'

"enum accessors"
DirectionEnum class>>input
    ^ self new value: #input

DirectionEnum class>>output
    ^ self new value: #output

"getter/setters"
DirectionEnum>>value
    ^ value

DirectionEnum>>value: aValue
    value := aValue

"comparing"
DirectionEnum>>= anEnum
    ^ self class = anEnum class and: [ self value = anEnum value ]

DirectionEnum>>hash
    ^ self class hash bitXor: self value hash

"printing"
DirectionEnum>>printOn: aStream
    super printOn: aStream.
    aStream << '(' << self value asString << ')'

用法还是一样

Direction input.
DirectionEnum output asString. "'a DirectionEnum(output)'"

并且解决了琐碎方法中提到的问题

显然这是更多的工作,但结果更好。如果你有很多枚举,那么将基本行为移动到一个新的超类 Enum 可能是有意义的,然后 DirectionEnum 只需要包含 class-side 方法。

由于 Smalltalk 是动态类型的,无论如何都不能将变量的值限制为对象的子集,因此除了通过枚举名称进行命名空间之外,枚举成员和全局常量之间没有区别。

编辑:有关如何定义枚举常量的选项,请参阅 Peter 的回答。只是让我提一下,如果足以满足您的需要,您也可以直接使用符号。 direction := #input. direction := #output

对于简单的情况,只需按照 Peter 的建议使用符号 - 您也可以将可能的值存储在 IdentityDictionary.

如果您指的是 Java 中可用的更强大的枚举类型(它们不仅仅是一种命名常量;它们可以具有行为、复杂属性等),那么我会比 Peter 更进一步,只是为每个枚举类型创建一个子类。即使你在谈论大量的枚​​举(你的用例似乎只需要两个),也不要害怕有很多子类,其中只有一个或两个方法只是用来区分它们,大部分工作在公共超类中完成。

最接近枚举类型的 Smalltalk 特性是 SharedPool(a.k.a。PoolDictionary)。因此,如果您要将某些枚举从 Java 移植到 Smalltalk,您可能需要使用 SharedPool。方法如下:

对于类型中的每个枚举,您将在池中定义一个关联,其中 key 类型名称和 value 类型值。

在某些方言中 PoolDictionaries 是词典,在 Pharo 中它们是 SharedPool 的子class。因此,在 Pharo 中,您将所有类型名称声明为 class 变量。然后在初始化方法中将值与键相关联(class 端)。

例如,您可以使用 class 变量 'Red', 'Green', 'Blue', 'Black', 'White' 等定义名为 ColorConstantsSharedPool 的子 class,如下所示:

SharedPool
  subclass: #ColorConstants
  instanceVariableNames: ''
  classVariableNames: 'Red Green Blue Black White'
  poolDictionaries: ''
  package: 'MyPackage'

要将名称与值相关联,请在以下行添加 class 端初始化方法:

ColorConstants class >> initialize
  Red := Color r: 1 g: 0 b: 0.
  Green := Color r: 0 g: 1 b: 0.
  Blue := Color r: 0 g: 0 b: 1.
  Black := Color r: 0 g: 0 b: 0.
  White := Color r: 1 g: 1 b: 1.
  "and so on..."

评估 ColorConstants initialize 后,您将能够在 class

中使用池
Object
  subclass: #MyClass
  instanceVariableNames: 'blah'
  classVariableNames: ''
  poolDictionaries: 'ColorConstants'
  package: 'MyPackage'

MyClass(及其子classes)中,您可以按名称引用颜色:

MyClass >> displayError: aString
   self display: aString foreground: Red background: White

MyClass >> displayOk: aString
   self display: aString foreground: Green background: Black