5

我想就无副作用的二传手能走多远征求你的意见。

考虑以下示例:

Activity activity;
activity.Start    = "2010-01-01";
activity.Duration = "10 days";   // sets Finish property to "2010-01-10"

请注意,日期和持续时间的值仅供参考。

因此,将 setter 用于任何属性StartFinish因此Duration会更改其他属性,因此不能被视为无副作用。同样适用于Rectangle类的实例,其中 setter for正在更改等X的值。TopBottom

问题是在使用 setter(具有更改逻辑相关属性的值的副作用)和使用方法(无论如何都不能更具描述性)之间划清界限。例如,定义一个调用的方法SetDurationTo(Duration duration)也不反映 Start 或 Finish 将被更改。

4

5 回答 5

10

我认为您误解了“副作用”一词,因为它适用于程序设计。设置一个属性一个副作用,不管它改变了多少内部状态,只要它改变某种状态。“无副作用的设置器”不会很有用。

副作用是您希望在属性getter上避免的事情。读取属性的值是调用者不希望改变任何状态的事情(即导致副作用),所以如果这样做,它通常是错误的或至少是有问题的(有例外,例如延迟加载)。但无论如何,getter 和 setter 都只是方法的包装器。Duration就 CLR 而言,该属性只是set_Duration方法的语法糖。

这正是类等抽象的含义——提供粗粒度操作,同时保持一致的内部状态。如果您故意避免在单个属性分配中产生多个副作用,那么您的类最终只不过是愚蠢的数据容器。

所以,直接回答这个问题:我在哪里画线?无处,只要方法/属性确实如其名称所暗示的那样。如果设置Duration也改变了ActivityName,那可能是个问题。如果它改变了Finish属性,那应该是显而易见的;应该不可能改变Duration和同时拥有StartFinish保持不变。OOP 的基本前提是对象有足够的智能来自己管理这些操作。

如果这在概念级别上困扰您,那么根本没有 mutator 属性 - 使用具有只读属性的不可变数据结构,其中所有必要的参数都在构造函数中提供。然后有两个重载,一个采用Start/ Duration,另一个采用Start/ Finish。或者只将其中一个属性设为可写 - 让我们Finish保持一致Start- 然后设为Duration只读。使用可变和不可变属性的适当组合,以确保只有一种方法可以更改某个状态。

否则,不要太担心这个。属性(和方法)不应有意外或未记录的副作用,但这是我将使用的唯一准则。

于 2010-03-16T03:40:12.250 回答
1

就个人而言,我认为保持一致状态具有副作用是有意义的。就像你说的,改变逻辑相关的值是有意义的。从某种意义上说,副作用是意料之中的。但重要的是要明确这一点。也就是说,很明显该方法正在执行的任务具有某种副作用。因此,SetDurationTo您可以调用您的 function而不是ChangeDurationTo,这意味着正在发生其他事情。AdjustDurationTo您也可以通过使用调整持续时间并传入值的函数/方法来以另一种方式执行此操作delta。如果您将该功能记录为具有副作用,这将有所帮助。

我认为另一种看待它的方法是看看是否会出现副作用。在您的 Rectangle 示例中,我希望它能够更改topbottom保持内部一致状态的值。我不知道这是否是主观的;这对我来说似乎很有意义。与往常一样,我认为文档胜出。如果有副作用,请认真记录。最好通过方法的名称和支持文档。

于 2010-03-16T03:24:32.537 回答
1

一种选择是使您的类不可变,并让方法创建并返回已更改所有适当值的类的新实例。然后没有副作用或二传手。想想DateTime你可以在哪里调用类似的东西AddDaysAddHours它将返回一个DateTime应用了更改的新实例。

于 2010-03-16T03:24:40.033 回答
0

我一直遵循一般规则,public即不允许 setter 对没有副作用的属性进行设置,因为公共 setter 的调用者无法确定会发生什么,但当然,修改程序集本身的人应该有一个好主意,因为他们可以看到代码。

当然,有时您必须为了可读性、使您的对象模型合乎逻辑或只是为了让事情正常工作而打破规则。就像你说的,总的来说,这确实是一个偏好问题。

于 2010-03-16T03:23:28.603 回答
-2

我认为这主要是一个常识问题。

在这个特定的示例中,我的问题不在于您拥有调整“相关”属性的属性,而是您拥有获取字符串值的属性,然后您可以在内部解析为 DateTime(或其他)值。

我宁愿看到这样的东西:

Activity activity;
activity.Start    = DateTime.Parse("2010-01-01");
activity.Duration = Duration.Parse("10 days");

也就是说,您明确指出您正在解析字符串。允许程序员在适当的时候指定强类型对象。

于 2010-03-16T03:24:37.133 回答