2

我经常从测试驱动开发人员那里听到,让一个函数隐含地获取大量信息是一件坏事。我可以看到从测试的角度来看这会很糟糕,但从封装的角度来看有时不是必要的吗?想到以下问题:

使用 Random 和 OrderBy 是否是一个好的 shuffle 算法?

基本上,有人想在 C# 中创建一个函数来随机打乱数组。有几个人告诉他,随机数生成器应该作为参数传入。对我来说,这似乎是对封装的严重违反,即使它确实使测试更容易。数组混洗算法是否需要任何状态,而不是它正在混洗调用者不必关心的实现细节的数组?获取此信息的正确位置难道不是隐式的,可能来自线程本地单例吗?

4

3 回答 3

10

我认为它不会破坏封装。数组中唯一的状态是数据本身——“随机源”本质上是一种服务。为什么一个数组自然会有一个相关的随机源?为什么必须是单例?具有不同要求的不同情况如何——例如速度与密码安全随机性?java.util.Random有一个子类是有原因的SecureRandom:) 也许通过大量的努力和观察,洗牌的结果是否可以预测并不重要——或者它确实如此。这将取决于上下文,这是 shuffle 算法不应该关心的信息。

一旦您开始将其视为一项服务,就可以将其作为依赖项传入。

是的,您可以从线程本地单例中获取它(事实上,我将在接下来的几天内写一篇关于它的博客),但我通常会对其进行编码,以便调用者做出决定。

“作为服务的随机性”概念的一个好处是它可以实现可重复性——如果你的测试失败了,你可以通过Random一个特定的种子并知道你总是会得到相同的结果,这使得调试更容易。

当然,始终可以选择将其Random设为可选 - 如果调用者不提供自己的,则使用线程本地单例作为默认值。

于 2009-11-03T14:18:03.280 回答
4

是的,这确实打破了封装。与大多数软件设计决策一样,这是两种对立力量之间的权衡。如果您封装了 RNG,那么您很难为单元测试进行更改。如果将其设为参数,则用户可以轻松更改 RNG(并可能出错)。

我个人的偏好是使其易于测试,然后为最终用户提供默认实现(在这种特殊情况下创建自己的 RNG 的默认构造函数)和良好的文档。添加带有签名的方法

public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source)

使用当前系统时间作为其种子创建一个Random将处理此方法的大多数正常用例。原始方法

public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)

could be used for testing (pass in a Random object with a known seed) and also in those rare cases where a user decides they need a cryptographically secure RNG. The one-parameter implementation should call this method.

于 2009-11-03T14:20:25.583 回答
1

我不认为这违反了封装。

你的例子

我会说能够提供 RNG 是该课程的一个特点。我显然会提供一种不需要它的方法,但我可以看到复制随机化可能有用的时候。

如果阵列混洗器是使用 RNG 生成关卡的游戏的一部分会怎样。如果用户想保存关卡并稍后再次播放,存储 RNG 种子可能更有效。

一般情况

像这样具有单一任务的简单类通常不需要担心泄露其内部工作原理。它们封装的是任务的逻辑,而不是该逻辑所需的元素。

于 2009-11-03T14:18:27.163 回答