【问题标题】:How do I get a message sender in SmallTalk (Pharo)?如何在 SmallTalk (Pharo) 中获取消息发送者?
【发布时间】:2012-06-27 05:37:17
【问题描述】:

我无法在 SmallTalk 中获取消息的发件人。我想要完成的是从第一个方法(A)调用的另一个方法(B)修改方法(A)的返回值。再次... A 调用 B,我希望 B 从 A 的上下文中返回一个值。

示例代码:

这将是 A:

A

| aResult aPartialResult |

aPartialResult := self B.

"do things with aPartialResult"

^aResult.

这将是 B:

B

| aResult |

[ aResult := "do something" ]
                        on: Exception
                        do: ["make A return something"].
^aResult.

问题是我希望 B 中可能引发的异常也可以在 B 中处理。这就是为什么我不只是在 B 中引发异常以在 A 中处理它并轻松地从那里返回。

我以为我可以使用 thisContext 来做到这一点,但发件人是 nil。也可以得到一个关于为什么会这样的答案......

提前致谢!

【问题讨论】:

  • 这不是打破了封装,为什么 B 应该知道关于 A 的任何事情?
  • B 和 A 是 同一个对象上的方法。
  • @Guillermo 我仍在努力思考您正在尝试的内容...除了指定返回值之外,您是否在 B 的错误处理程序中做任何事情?这是您的目标,即您是否坚持 A/B 部门,还是那么灵活?如果你能以一种不那么以实现为中心的方式来陈述具体问题,那真的很棒。我们可以很容易地回答你的问题,但这在 Smalltalk 中似乎很罕见,直觉告诉我们有更好的方法来解决它......
  • 如果您使用AB 以外的其他名称作为方法名称,它可能会使您的眼睛更容易一些。 Smalltalk 有很强的约定性,按照约定,方法名称以小写字母开头,并且缩写不是要走的路,即使在一次性示例中也是如此。试着打电话给他们methodOnemethodTwo,它看起来会好很多。此外,使用冠词“a”(或“an”)作为前缀的变量通常用于方法参数,而不是临时变量。这背后的原因是为了强调传入的内容是一个可能的值。只是我的 2 美分 ;-)
  • 我会回复一个答案,因为我在这里无法容纳足够的字符...

标签: reflection exception-handling metaprogramming smalltalk pharo


【解决方案1】:

Guillermo,异常处理可以无缝替换这里的一些坏主意:

  1. 使用 thisContext(这几乎从来没有必要,通常 一个坏主意)
  2. 传递字符串,例如'1|',用户界面无效CartIdErrorMessage
  3. 使用 return:与这些字符串一起传递错误

另外,retrieveCart:onErrorReturnFrom: 做得太多了。有了所有的错误处理程序,实际的逻辑就会丢失。

所以,我要做的第一件事是创建错误子类来表示您的领域概念,例如AddBookError, CartExpiredError, InvalidCartError

那你就设置错误信息,可能是这样的:

CartExpiredError>>initialize

    super initialize.
    self messageText: '1|', UserInterface cartHasExpiredErrorMessage.

接下来的事情(实际上是两个步骤)是用私有访问器替换原始字典方法,它可以使用您的新错误类,如下所示:

timestampFor: aCartId

    ^ cartCreationDateAndTime at: aCartId ifAbsent: [ InvalidCartError signal ].

cartNumber: aCartId 

     ^ carts at: aCartId ifAbsent: [ InvalidCartError signal ].

Cart>>add: aQuantity booksWithISBN: aBookISBN

    fail ifTrue: [ AddBookError signal ].

现在,retrieveCart:onErrorReturnFrom: 可以变成:

retrieveCart: aCartId

    | aCartCreationDateAndTime |
    aCartCreationDateAndTime := self timestampFor: aCartId.
    Time now > (aCartCreationDateAndTime + 30 minutes) ifTrue:  [ CartExpiredError signal ].
    ^ self cartNumber: aCartId.

最后,大大简化的 A 变为:

add: aQuantity booksWithISBN: aBookISBN toCart: aCartId

     | aCart |
    [aCart := self retrieveCart: aCartId.
    aCart add: aQuantity booksWithISBN: aBookISBN]
        on: Error
        do: [ :e |  ^ e messageText ].
    ^ '0|OK'.

这仍然可以被清除(例如,为所有在 messageText 前面加上 '1|' 的错误类创建一个超类),显然你必须将这个简化版本应用到你的实际项目中,但是你能开始看到吗例外如何让您的生活更轻松?

Here 是代码的工作模型,在 github 上通过测试

n.b.我注意到的另一件事是CartCreationDateAndTime。将其作为购物车的属性似乎更自然,但在实际应用中这可能没有意义......

【讨论】:

  • 不错。我喜欢它胜过我教授的回答(当然,它比我的要好……)。就像你说的,关于 aCartCreationDateAndTime 的评论是一个上下文问题。购物车的失效是一种与服务器资源节省相关的垃圾收集需求,因此不属于商店模型。或者至少我的课程是这样的。谢谢大家的回答!
【解决方案2】:

一个简单的方法是 A 将一个带有 return 的 Block 传递给 B,例如:

A
   | aResult aPartialResult |
   aPartialResult := self BonSpecialConditionDo: [:partial | ^partial].
   ...snip...

然后

BonSpecialConditionDo: aBlock
    | partialResult |
    partialResult := self doSomethingPartial.
    ^[self doSomething]
        on: SomeException
        do: [:exc | aBlock value: partialResult]

当心,捕捉异常被认为是危险的(你捕捉到太多东西)。

编辑:我刚刚在处理程序中删除了一个不必要的返回 ^

编辑:使用超能力(但不要用锤子杀死苍蝇)

B
    | partialResult |
    partialResult := self doSomethingPartial.
    ^[self doSomething]
        on: SomeException
        do: [:exc | thisContext home sender home return: partialResult ]

我以为你可以通过Exception exc访问thisContext(这是实例变量handlerContext),但是似乎没有任何方便的消息可以访问这个内部状态......

【讨论】:

  • 这部分解决了我的问题,但是 B 应该能够让 A 返回一系列结果,而不仅仅是一个。我也可以做类似 BOnEvent1Do: BOnEvent2Do: 之类的事情,但我试图让 A 尽可能干净。此外,如果最终 B 被更改以处理新错误,我将不得不更正 B 的所有发件人。
  • @GuillermoAres 您可以为每个 partialResult 重复使用相同的块,因为该块只会将任何 partialResult 的返回转发给 A 的发送者,所以它将是 BOnAnyEventDo:但是现在您暴露了您的意图,我也喜欢肖恩的解决方案。取决于 messageText 编码是否对 CartExpiredError 的所有使用有价值或非常特定于处理程序......
【解决方案3】:

知道了!

这是一个:

A

| aResult aPartialResult |

aPartialResult := self BOnErrorReturnFrom: thisContext.

"do things with aPartialResult"

^aResult.

这将是 B:

BOnErrorReturnFrom: aContext

| aResult |

[ aResult := "do something" ]
                        on: Exception
                        do: [aContext return: "whatever you want :)"].
^aResult.

当我意识到关键字“thisContext”在 BlockClosure 中使用时,它不会返回声明块的 ContextPart,而是返回其他内容(但仍然不确定它是什么),这并不难.

回应肖恩:

我试图这样做是为了避免重复代码。实现 A 和 B 的对象是 REST 接口的内部(模型端)部分,所以我希望它返回一个字符串并且只返回一个字符串(我不希望异常或与字符串对象不同的任何东西通过) .在我的具体问题中,A(很抱歉打破了 Smalltalk 消息命名约定,但我认为现在更改它会导致进一步的混乱......)收到一个购物车 ID 并将对该购物车做一些事情。 A 必须使用该 ID 检索购物车,对购物车进行一些验证,并在出现错误时返回临时消息(始终作为字符串)。此检索和验证代码将在必须检索购物车的每条消息中重复。

这是我最终得到的正确代码:

这是A:(不要注意#try 消息。它只是确保在没有被转换为字符串的情况下不会出现异常。如果有人知道如何以更好的方式做到这一点,请告诉我如何!)

add: aQuantity booksWithISBN: aBookISBN toCart: aCartId

    | aCart aContext |

    aContext := thisContext.

    ^self try: [

        aCart := self retrieveCart: aCartId onErrorReturnFrom: aContext.

        [aCart add: aQuantity booksWithISBN: aBookISBN]
                                                    on: Error
                                                    do: [ :anError | ^'1|', (self formatAsResponse: anError messageText) ].

        ^'0|OK'.
    ].

这是乙:

retrieveCart: aCartId onErrorReturnFrom: aContext

    | aCartCreationDateAndTime aCart |

    [aCartCreationDateAndTime := cartCreationDateAndTime at: aCartId asInteger.]
                                                                                    on: KeyNotFound 
                                                                                    do: [ aContext return: ('1|', (UserInterface invalidCartIdErrorMessage)).].

    (systemClock now > (aCartCreationDateAndTime + 30 minutes)) 
                                                            ifTrue:  [aContext return: ('1|', (UserInterface cartHasExpiredErrorMessage))].

    [aCart := carts at: aCartId asInteger.]
                                    on: KeyNotFound
                                    do: [ aContext return: ('1|', (UserInterface invalidCartIdErrorMessage))].

    ^aCart.

【讨论】:

  • 听起来你想在这里继续。检查应该有一个实现延续的库
  • 为什么不在#retrieveCart: 方法中使用简单异常。所以你可以抓住它并从中返回。 [self retrieveCart: aCartId] on: UserInterfaceMessageException do: [| msgException | ^'1|', msgException message ]
  • @mathk:有一个:ss3.gemstone.com/ss/Control。不过,在这里继续似乎有点过头了。
  • thisContext 在块中返回块执行的上下文(BlockContext),当然!如果你想要“主上下文”——触发块执行的方法上下文——使用thisContext home
  • @Frank 这就是为什么我提出使用异常机制的第二种解决方案
【解决方案4】:

一个

| aResult aPartialResult |

aPartialResult := self B.

“用aPartialResult做事”

^aResult.

B

|结果|

[ aResult := "做某事" ] 上:例外 做:[“让A返回一些东西”]。 ^一个结果。

我会写成

一个

| aResult aPartialResult |

aPartialResult := 自我 B. aPartialResult ifNotNil:[

“用 aPartialResult 做事”].

^aResult.

B

|结果|

[ aResult := "做某事" ] 上:例外 做:[:e|^nil]。 ^一个结果。

那就是我!!!

【讨论】:

    猜你喜欢
    • 2012-04-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-27
    • 1970-01-01
    • 1970-01-01
    • 2019-11-04
    相关资源
    最近更新 更多