在smalltalk中重构一个方法

Refactoring a method in smalltalk

我是 Smalltalk (Squeak) 的新用户(实际上是在课程中学习它)。 我有一种方法来检查一个矩形是否等于给定的矩形,如下所示:

isEqual:givenRec
    self a = givenRec a
    ifTrue: [
        self b = givenRec b
        ifTrue: [
            ^true
        ].
        ^false
    ].
    self b = givenRec a
    ifTrue: [
        self a = givenRec b
        ifTrue: [
            ^true
        ].
        ^false
    ].
    ^false

我的问题是 - 有没有更好的写法?让它更紧凑?

另外 - 为什么我不能引用 a 这是 instanceVariableNames with self inside methods? 感谢您的帮助!

编辑:

class 是这样定义的:

MyShape subclass: #MyTriangle
    instanceVariableNames: 'a b c'
    classVariableNames: ''
    poolDictionaries: ''
    category: 'Ex2'

MyShape 只是从 Object 派生而来,什么都没有。

你可以让它更紧凑,是的。

首先,除了#ifTrue:还有#ifFalse:#ifTrue:ifFalse:,大致就是if-not--then和if--then--else的作用。

而且,我们已经有了逻辑 AND 条件,所以为什么不使用它:

isEqual: givenRec

    (self a = givenRec a and: [self b = givenRec b])
        ifTrue: [^ true].
    (self b = givenRec a and: [self a = givenRec b])
        ifTrue: [^ true].
    ^false

#ifTrue:ifFalse:

isEqual: givenRec

    (self a = givenRec a and: [self b = givenRec b])
        ifTrue: [^ true]
        ifFalse: [^ (self b = givenRec a and: [self a = givenRec b])]

另外,我们可以return围绕整个语句:

isEqual: givenRec

    ^ (self a = givenRec a and: [self b = givenRec b])
        ifTrue: [true]
        ifFalse: [self b = givenRec a and: [self a = givenRec b]]

但是ifTrue: [true]有点多余,我们用#or:

isEqual: givenRec

    ^ (self a = givenRec a and: [self b = givenRec b]) or: 
      [self b = givenRec a and: [self a = givenRec b]]

很不错,我们也很容易看出逻辑结构。 (请注意,我从常见的格式风格出发,指出两个逻辑表达式的异同)。

我们现在只有一个return^,没有#ifTrue:…


对于实例变量问题:

当您像以前那样在 class 中定义一些实例变量时,您可以在代码中直接使用它们来访问它们:

Object subclass: #Contoso
    instanceVariableNames: 'things'
    classVariableNames: ''
    poolDictionaries: ''
    category: 'Examples'
isThingPlusThreeSameAs: anObject

    ^ thing + 3 = anObject

但通常情况下,最好通过 getterssetters 引用实例变量,通常称为 accessors。您必须手动编写它们或使用浏览器中 class 的 第二个 上下文菜单(通过“更多...”)的 'create inst var accessor' 菜单项:

这将生成形式为

的访问器方法
thing

    ^ thing
thing: anObject

    thing := anObject

您还可以像这样在其他方法中使用它们

isThingPlusThreeSameAs: anObject

    ^ self thing + 3 = anObject

我会把方法写成

equals: aTriangle
  a = aTriangle a ifFalse: [^false].
  b = aTriangle b ifFalse: [^false].
  ^c = aTriangle c

顺便说一下,我使用的是选择器 #equals: 而不是 #isEqual: 因为后者在英语中读起来不太好(如果是 #isEqualTo:,但 #equals: 更短)。

我要在这里补充的另一点是,您可以重新定义 #= 而不是添加新的比较选择器。然而,在那种情况下,您必须注意一些额外的事情:

= aTriangle
  self class = aTriangle class ifFalse: [^false].
  a = aTriangle a ifFalse: [^false].
  b = aTriangle b ifFalse: [^false].
  ^c = aTriangle c

class 检查的原因是为了确保 #= 是可靠的,即它不会发出 MessageNotUnderstood 异常信号。另一件事是,每次你(重新)定义 #= 时,你也应该(重新)定义 #hash,使得 t1 hash = t2 hash 每当 t1 = t2。例如

hash
  ^(a hash + b hash + c hash) hash

请注意,#hash 的这个提议不是最好的,但这不是讨论编写好的 #hash 函数的问题的地方。

更新

这是我的版本,顺序无关紧要

equals: aTriangle
  | sides |
  self = aTriangle ifTrue: [^true].
  sides := Set with: a with: b with: c.
  (sides includes: aTriangle a) ifFalse: [^false].
  (sides includes: aTriangle b) ifFalse: [^false].
  ^(sides includes: aTriangle c)

第一个比较是在顺序相同时避免Set的加速方法

你可能不应该这样写,但在某些情况下元编程很有用

isEqual: givenRec
  #(a b c) do: [ :selector |
    (self perform: selector) = (givenRec perform: selector) ifFalse: [
      ^false]].
  ^true