无副作用二传手的方法

Posted

技术标签:

【中文标题】无副作用二传手的方法【英文标题】:Approach to side-effect-free setters 【发布时间】:2010-03-16 03:16:34 【问题描述】:

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

考虑以下示例:

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

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

因此,对 StartFinishDuration 的任何属性使用 setter 将因此更改其他属性,因此不能被视为无副作用。 同样适用于Rectangle 类的实例,其中X 的设置器正在更改TopBottom 的值等等。

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

【问题讨论】:

【参考方案1】:

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

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

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

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

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

否则,不要太担心这个。属性(和方法)不应该有 unintendedundocumented 副作用,但这是我会使用的唯一准则。

【讨论】:

谢谢,这就是我一直在寻找的东西,我从来没有想过“无副作用的设置器”实际上是不可能的,因为它会改变状态。正如您指出的那样,CLR 无论如何都会转换为方法。 是的,setter 的副作用是设置它引用的成员变量。但另一个副作用是修改其他成员变量。 我只是在想同样的事情,我什至会说成员变量的设置不是副作用,而是设置器的预期效果。更改其他成员变量是一个副作用。 可能说设置成员字段不是副作用,但你是不正确的。该术语有一个非常具体的定义; 副作用表示操作改变状态,周期。它可能根本不会影响任何字段 - 也许它会保存到磁盘或从套接字发送数据。这就是为什么我要区分unintendedundocumented(或者可能是undesired 的副作用)。然后它变成一个简单的问题:这个附加副作用是否预期?重要的不是变化的数量,而是变化的什么 好的,我已经完成了我的功课并且同意你的说法。再次感谢您的宝贵意见。【参考方案2】:

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

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

【讨论】:

【参考方案3】:

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

【讨论】:

【参考方案4】:

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

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

【讨论】:

【参考方案5】:

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

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

我更愿意看到这样的东西:

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

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

【讨论】:

正如我所提到的“请注意,日期和持续时间的值仅供参考。”所以你的评论确实没有解决所提出的问题,但无论如何谢谢你......我知道有人会指出这个;-)

以上是关于无副作用二传手的方法的主要内容,如果未能解决你的问题,请参考以下文章

ios开发h5页面有一个加载框为啥我在wkwebview上不能弹出来

javascript 二传手术方法

Flex中,删除事件侦听器,并结合二传手

二传手一直自称递归

让二传手返回“this”是不好的做法吗?

javascript 实现内置的吸气和二传手,控制私有对象属性的访问