是否传入应该知道隐含的封装参数?

时间:2009-11-03 14:06:26

标签: language-agnostic testing singleton encapsulation tradeoff

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

Is using Random and OrderBy a good shuffle algorithm?

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

3 个答案:

答案 0 :(得分:10)

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

一旦你开始将它视为一种服务,它就会作为一种依赖传递它。

是的,你可以从线程本地单例中获取它(事实上我将在接下来的几天内准确地发布博客)但我通常会对它进行编码以便< em>来电者做出决定。

“随机性即服务”概念的一个好处是它具有可重复性 - 如果您的测试失败了,您可以使用特定种子传递Random并知道您将总是得到相同的结果,这使调试更容易。

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

答案 1 :(得分:4)

是的,确实会中断encapsulation。与大多数软件设计决策一样,这是两种对立力量之间的权衡。如果您封装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)

可用于测试(传入具有已知种子的Random对象),也可用于用户决定需要加密安全RNG的极少数情况。单参数实现应该调用此方法。

答案 2 :(得分:1)

我不认为这违反了封装。

您的示例

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

如果数组洗牌器是使用RNG进行级别生成的游戏的一部分,该怎么办?如果用户想要保存级别并在以后再次播放,则存储RNG种子可能更有效。

一般情况

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