隐式传递应该知道的参数是否违反了封装性?
我经常从测试驱动开发人员那里听到,让一个函数隐式获取大量信息是一件坏事。我可以看出,从测试的角度来看这会很糟糕,但从封装的角度来看,有时这不是必要的吗?我想到以下问题:
正在使用 Random 和 OrderBy一个好的洗牌算法?
基本上,有人想在 C# 中创建一个函数来随机洗牌数组。有几个人告诉他,随机数生成器应该作为参数传入。对我来说,这似乎严重违反了封装性,即使它确实使测试变得更容易。事实上,数组混洗算法除了混洗数组之外还需要任何状态,调用者不必关心实现细节吗?难道获取此信息的正确位置不是隐式的(可能来自线程本地单例)吗?
I often hear around here from test driven development people that having a function get large amounts of information implicitly is a bad thing. I can see were this would be bad from a testing perspective, but isn't it sometimes necessary from an encapsulation perspective? The following question comes to mind:
Is using Random and OrderBy a good shuffle algorithm?
Basically, someone wanted to create a function in C# to randomly shuffle an array. Several people told him that the random number generator should be passed in as a parameter. This seems like an egregious violation of encapsulation to me, even if it does make testing easier. Isn't the fact that an array shuffling algorithm requires any state at all other than the array it's shuffling an implementation detail that the caller should not have to care about? Wouldn't the correct place to get this information be implicitly, possibly from a thread-local singleton?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我不认为它破坏了封装。数组中唯一的状态是数据本身 - 而“随机源”本质上是一种服务。为什么数组自然应该有关联的随机源?为什么它必须是单例?具有不同要求的不同情况怎么样——例如速度与加密安全随机性?
java.util.Random
有一个SecureRandom
子类是有原因的 :) 也许通过大量的努力和观察,洗牌的结果是否可预测并不重要 - 或者也许确实如此。这将取决于上下文,这是随机播放算法不应该关心的信息。一旦您开始将其视为一项服务,那么将其作为依赖项传递就有意义了。
是的,您可以从线程本地单例中获取它(事实上,我将在接下来的几天里在博客中详细介绍这一点),但我通常会对其进行编码,以便调用者 才能做出决定。
“随机性即服务”概念的一个好处是它可以实现可重复性 - 如果您的测试失败,您可以传入带有特定种子的
Random
并知道您会总是得到相同的结果,这使得调试更容易。当然,总是可以选择将
Random
设为可选 - 如果调用者不提供自己的单例,则使用线程本地单例作为默认值。I don't think it breaks encapsulation. The only state in the array is the data itself - and "a source of randomness" is essentially a service. Why should an array naturally have an associated source of randomness? Why should that have to be a singleton? What about different situations which have different requirements - e.g. speed vs cryptographically secure randomness? There's a reason why
java.util.Random
has aSecureRandom
subclass :) Perhaps it doesn't matter whether the shuffle's results are predictable with a lot of effort and observation - or perhaps it does. That will depend on the context, and that's information that the shuffle algorithm shouldn't care about.Once you start thinking of it as a service, it makes sense that it's passed in as a dependency.
Yes, you could get it from a thread-local singleton (and indeed I'm going to blog about exactly that in the next few days) but I would generally code it so that the caller gets to make that decision.
One benefit of the "randomness as a service" concept is that it makes for repeatability - if you've got a test which fails, you can pass in a
Random
with a specific seed and know you'll always get the same results, which makes debugging easier.Of course, there's always the option of making the
Random
optional - use a thread-local singleton as a default if the caller doesn't provide their own.是的,这确实破坏了封装。与大多数软件设计决策一样,这是两种对立力量之间的权衡。如果封装 RNG,那么单元测试就很难更改。如果将其设为参数,那么用户就可以轻松更改 RNG(并且可能会出错)。
我个人的偏好是使其易于测试,然后为最终用户提供默认实现(在本例中为创建自己的 RNG 的默认构造函数)和良好的文档。添加一个带有使用当前系统时间作为种子创建
Random
签名的方法,可以处理该方法的大多数正常用例。原始方法
可用于测试(传入带有已知种子的随机对象),也可用于用户决定需要加密安全 RNG 的极少数情况。单参数实现应调用此方法。
Yes, that does break encapsulation. As with most software design decisions, this is a trade-off between two opposing forces. If you encapsulate the RNG then you make it difficult to change for a unit test. If you make it a parameter then you make it easy for a user to change the RNG (and potentially get it wrong).
My personal preference is to make it easy to test, then provide a default implementation (a default constructor that creates its own RNG, in this particular case) and good documentation for the end user. Adding a method with the signature
that creates a
Random
using the current system time as its seed would take care of most normal use cases of this method. The original methodcould 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.我不认为这违反了封装性。
你的例子
我想说的是,能够提供 RNG 是该课程的一个特点。显然,我会提供一种不需要它的方法,但我可以看到有时能够重复随机化可能很有用。
如果数组洗牌器是使用 RNG 生成关卡的游戏的一部分会怎样?如果用户想要保存关卡并稍后再玩,则存储 RNG 种子可能会更有效。
一般情况
像这样具有单一任务的简单类通常不需要担心泄露其内部工作原理。它们封装的是任务的逻辑,而不是该逻辑所需的元素。
I don't think this violates encapsulation.
Your Example
I would say that being able to provide an RNG is a feature of the class. I would obviously provide a method that doesn't require it, but I can see times where it may be useful to be able to duplicate the randomization.
What if the array shuffler was part of a game that used the RNG for level generation. If a user wanted to save the level and play it again later, it may be more efficient to store the RNG seed.
General Case
Simple classes that have a single task like this typically don't need to worry about divulging their inner workings. What they encapsulate is the logic of the task, not the elements required by that logic.