接近无副作用的二传手

时间:2010-03-16 03:16:35

标签: c# properties setter

我希望得到你对无副作用的制定者有多远的看法。

考虑以下示例:

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

请注意,日期和持续时间的值仅用于指示目的。

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

问题是你在哪里使用setter之间画一条线,它们具有改变逻辑相关属性值的副作用,以及使用方法,无论如何这些方法都不会更具描述性。例如,定义名为SetDurationTo(Duration duration)的方法也不会反映Start或Finish将被更改。

5 个答案:

答案 0 :(得分:8)

我认为你误解了“副作用”一词,因为它适用于程序设计。设置属性 是一种副作用,无论它改变多少内部状态或多少内部状态,只要它改变某些类型的状态。 “无副作用的二传手”不会非常有用。

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

这正是类的抽象意图 - 在保持一致的内部状态的同时提供粗粒度操作。如果你故意试图避免在一个属性赋值中产生多个副作用,那么你的类最终只不过是愚蠢的数据容器。

所以,直接回答这个问题:我在哪里划线?只要方法/属性实际上符合其名称所暗示的那样,就没有任何地方。如果设置Duration也更改了ActivityName,则可能会出现问题。如果它改变了Finish属性,那应该是显而易见的;它应该无法更改Duration并让StartFinish保持不变。 OOP的基本前提是对象足够智能,可以自行管理这些操作。

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

否则,不要太担心这个。属性(和方法)不应该有非预期的未记录的副作用,但这是我将使用的唯一指南。

答案 1 :(得分:1)

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

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

答案 2 :(得分:1)

一个选项是使您的类不可变,并让方法创建并返回更改了所有适当值的类的新实例。然后没有副作用或安装者。考虑类似DateTime之类的内容,您可以在其中调用AddDaysAddHours之类的内容,这些内容将返回应用更改的新DateTime实例。

答案 3 :(得分:0)

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

当然,为了使可读性,使对象模型合乎逻辑,或者只是为了使事情正常工作,有时候你必须打破规则。就像你说的那样,一般来说都是一个偏好的问题。

答案 4 :(得分:-2)

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

在这个特定的例子中,我的问题并不是你已经有了调整“相关”属性的属性,而是你已经获得了属性的字符串值,然后你将其内部解析为DateTime(或其他)值。

我更愿意看到这样的事情:

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

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