【问题标题】:What is the idiomatic way to define an enum type in Smalltalk?在 Smalltalk 中定义枚举类型的惯用方法是什么?
【发布时间】:2018-03-23 13:37:33
【问题描述】:

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

【问题讨论】:

  • 我不是 Java 程序员,所以我不确定您是否指的是在 Smalltalk 中定义常量的方法。如果是这种情况,您应该创建一个PoolDictionary 并在那里声明常量。如果您确认我的解释是正确的,我可以详细说明。
  • Mo enum 是一种类型,即 enum X 类型的变量只能具有为 X 定义的一组值 - 例如在这种情况下,x: = new Direction 表示 x 是输入或输出,不可能是其他任何东西。

标签: smalltalk pharo


【解决方案1】:

简单的方法

最简单的方法是让类端方法返回符号或其他基本对象(例如整数)。

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

Direction class>>input
    ^ #input

Direction class>>output
    ^ #output

及用法:

Direction input

主要缺点是:

  • 碰巧返回相同值的任何其他“枚举”将等于这个值,即使枚举不同(您可以返回例如^ self name + '::input'
  • 在调试过程中,您会看到对象的实际值,这对于基于数字的枚举尤其难看(呃……这个 7 是什么意思?

对象方法

更好的方法是创建自己的枚举对象并返回它的实例。

这样的对象应该:

  • 覆盖=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 只需要包含类端方法。

【讨论】:

  • 这是一个非常好的答案。我只想补充一点,您需要枚举的原因可能有三个。 1)您希望为某些常量(可以由诸如自动完成或重构浏览器之类的工具使用)有一个编程名称,并且琐碎的方法对此有好处; 2)您希望对某种“枚举”具有不同的功能,这就是对象方法的亮点; 3)您想限制分配给变量的内容:我一般您不希望在 smalltalk 中使用此功能,但您始终可以向 setter 添加断言或在 Pharo 中使用自定义验证 Slot
【解决方案2】:

最接近枚举类型的 Smalltalk 功能是 SharedPool(又名PoolDictionary)。因此,如果您要将一些枚举从 Java 移植到 Smalltalk,您可能需要使用SharedPool。这样做的方法如下:

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

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

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

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

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

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,你就可以在你的课堂上使用这个池

Object
  subclass: #MyClass
  instanceVariableNames: 'blah'
  classVariableNames: ''
  poolDictionaries: 'ColorConstants'
  package: 'MyPackage'

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

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

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        猜你喜欢
        • 2013-01-03
        • 2018-10-13
        • 1970-01-01
        • 1970-01-01
        • 2011-02-08
        • 2019-03-27
        • 1970-01-01
        • 2012-03-10
        相关资源
        最近更新 更多