现有库 类 应该如何扩展?

How should existing library classes be extended?

normalize: sum
    | strategy normalizingSum |
    strategy := sum collect: [ :each | each max: 0.0 ].
    normalizingSum := strategy sum.
    ^strategy collect: [ :each |
        normalizingSum strictlyPositive
        ifTrue: [ each / normalizingSum ]
        ifFalse: [ 1.0 / Action subclasses size ]
        ]

我想让上面的方法成为一个实例方法,而不是必须显式地将 sum 传递给它。问题不是我无法制作方法或无法将其放入 Array class。只是来自函数式语言,我发现自己完全偏离了这里对我的期望。

据我所知,作为一种约定,大多数 Pharo 方法直接在实例上工作,而在函数式语言中,人们会做的是定义一个类似于 C#/Java 中的静态方法的函数。

一种选择是将该方法放入 Pharo 中的元class,但 Pharo 的语法不太适合这种编程风格。例如,它没有管道运算符,这解释了我观察到的所有库代码中对实例方法的严重倾斜。

通过直接将方法放入其中来扩展标准库 class 对我来说感觉有点不对劲。当需要将更改推送到 Github 时,事情究竟会怎样?直接放到 Array class 感觉最终会变成版本控制的噩梦。

另一种选择是继承自Array。这可能对我现在做的问题没问题,但以后我想做一个不同的问题,不想分享实现。

我应该把它放在特征中并将特征添加到 Array 吗?

你绝对应该让它成为 Array 的实例,甚至更好的 Collection 因为你的代码适用于任何可以迭代的东西(并且有数字)。

原因是使用起来更清晰:

#(3 5 49 3 1) normalized

而不是:

SomeMisteriousThirdParty normalize: #(3 5 49 3 1)

此外,如果您有一些特殊的 collections 或其他 classes,他们可以定义自己的 normalized 版本来正确处理事情。

Pharo 的理念是没有人可以为您的项目创造完美的环境。因此,很容易更改已有的库以满足您的需要。

这样做的方法是使用扩展方法,其中 您的 包使用方法扩展了一些 other class。此功能在 Pharo(和一般的 Smalltalk)中存在了十多年,正是出于这个原因,当您需要扩展另一个 class 但将更改与您的代码一起版本化时。

虽然我们讨论规范化的话题,但值得一提的是,至少有两个大项目最有可能需要规范化来完成他们正在做的事情。一种是用于数据可视化的 Roassal,一种是用于各种计算的 Polymath。看看他们的工作,您可能会受益。

这里的问题来自这样一个事实,即我们只考虑您的 collection 应该回答的一条消息:#normalized。在您的代码中,collection sum 是需要 规范化 的 object。因此,很容易说 sum normalized。但是,我不建议将 #normalized 添加到 Array(或 Collection),因为您特定的 规范化 的逻辑不是 Array:它取决于 Action,这看起来是一个在您的项目上下文中有意义的概念。

这并不意味着您不应该用您的东西扩展 Array。这仅意味着如果您的扩展不是固有的,那么这样的决定将需要更多考虑。

在这种情况下,我的建议是分析您项目中的 collection 之类的 sum 是否具有它们固有的任何其他行为。在那种情况下,我会考虑用 class 来表示它们并添加到此 class 消息中,例如 #normalized,以及与这些 object 相关的任何其他消息。

举个例子,假设您的 class 名为 StrategyCollection。你可以在里面定义

strategy
  ^sum collect: [:each | each max: 0.0].

其中 sum 现在是您 class 的 ivar。然后你可以定义

normalized
  strategy := self strategy.
  normalizingSum := strategy sum.
  ^strategy collect: [:each |
    normalizingSum strictlyPositive
      ifTrue: [each / normalizingSum]
      ifFalse: [1.0 / Action subclasses size]]

顺便说一下,这可能是 re-written 作为

normalized
  | strategy normalizingSum |
  strategy := self strategy.
  normalizingSum := strategy sum.
  ^normalizingSum strictlyPositive
     ifTrue: [strategy collect: [:each | each / normalizingSum]]
     ifFalse: [strategy class new: sumstrategy size withAll: 1.0 / Action subclasses size]

如果除了 #max: 0 之外还有其他策略,您可以轻松调整上面的代码,通过使用 perform: 或使用 [= 的特殊子 class 使其更通用25=] 实现了自己的 #strategy.

版本

我还建议为表达式 Action subclasses size 添加一个方法。类似于

actionCount
  ^Action subclasses size

然后在#normalized中使用它。表达式 Action subclasses size 很脆弱,可能会出现在其他方法中。例如,如果明天你决定将 Action 的一些子 class 分组在另一个抽象子 class 下,那么 Action 的子class 的数量将不适应这样的重构。更一般地说,您的 object 的行为不应该取决于您组织代码的方式,因为那属于抽象的 meta-level。