我在 SmallTalk 中获取消息的发件人时遇到问题。我想要完成的是修改一个方法 (A) 的返回值,从第一个方法 (A) 调用的另一个方法 (B)。再。。。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 中处理它并从那里轻松返回。
我以为我可以使用这个上下文来做到这一点,但发送者为零。也得到一个关于为什么会这样的答案也没有什么坏处......
提前感谢!
Guillermo, 异常处理可以无缝地替换这里的一些坏主意:
- 使用thisContext(这几乎从来都不是必需的,通常一个坏主意)
- 传递字符串,例如'1|',用户界面无效CartIdErrorMessage
- 使用 return:用这些字符串传递错误
另外,检索购物车:onErrorReturnFrom:做得太多了。使用所有错误处理程序,实际逻辑会丢失。
因此,我要做的第一件事是创建错误子类来表示您的领域概念,例如AddBookError,CartExpireError,InvalidCartError
然后,您只需设置错误消息,例如:
CartExpiredError>>initialize
super initialize.
self messageText: '1|', UserInterface cartHasExpiredErrorMessage.
接下来的事情(实际上是两个步骤)是将原始字典方法替换为私有访问器,私有访问器可以使用新的 Error 类,如下所示:
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 ].
现在,检索购物车: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|"的错误类创建一个超类),显然你必须将这个简化版本应用到你的实际项目中,但你能开始看到异常如何让你的生活更轻松吗?
这是代码的工作模型,在github上通过了测试
注:注:我注意到的另一件事是CartCreationDateAndTime。将此作为购物车的属性似乎更自然,但也许这在实际应用程序中没有意义......
一个简单的方法是 A 传递一个块,并在内部返回给 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 ]
我以为您可以通过异常 exc 访问 thisContext(这是实例变量 handlerContext),但似乎没有任何方便的消息来访问这个内部状态......
知道了!
这是:
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 检索购物车,对购物车进行一些验证,并在出现错误时返回临时消息(始终为字符串)。此检索和验证代码将在必须检索购物车的每条消息中重复。
这是我最终得到的正确代码:
这是答:(不要注意 #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'.
].
这是 B:
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.
|一个
aResult aPartial Result |
a部分结果:= 自我 B。
"用部分结果做事"
^a结果。
|乙
结果 |
[ 结果 := "做某事" ] 开启:异常 做: ["让 A 返回一些东西"]。^a结果。
我会把它写成
|一个
aResult aPartial Result |
a部分结果:= 自我 B。aPartialResult ifNotNil:[
"用部分结果做事"]。
^a结果。
|乙
结果 |
[ 结果 := "做某事" ] 开启:异常 do: [:e|^nil].^a结果。
那么那只是我!!