无副作用设置器的方法
我想听听您对无副作用设置器能走多远的看法。
请考虑以下示例:
Activity activity;
activity.Start = "2010-01-01";
activity.Duration = "10 days"; // sets Finish property to "2010-01-10"
请注意,显示日期和持续时间的值仅用于指示目的。
因此,对 Start
、Finish
和 Duration
中的任何属性使用 setter 都会因此更改其他属性,因此不能被视为无副作用。 这同样适用于 Rectangle 类的实例,其中 X
的 setter 正在更改 Top
和 Bottom
的值,并且很快。
问题是,您如何在使用 setter 和使用方法之间划清界限,setter 具有更改逻辑相关属性的值的副作用,而方法无论如何也无法更具描述性。例如,定义名为 SetDurationTo(Durationuration)
的方法也不会反映 Start 或 Finish 将被更改。
I would like to get your opinion on as how far to go with side-effect-free setters.
Consider the following example:
Activity activity;
activity.Start = "2010-01-01";
activity.Duration = "10 days"; // sets Finish property to "2010-01-10"
Note that values for date and duration are shown only for indicative purposes.
So using setter for any of the properties Start
, Finish
and Duration
will consequently change other properties and thus cannot be considered side-effect-free.
Same applies for instances of the Rectangle
class, where setter for X
is changing the values of Top
and Bottom
and so on.
The question is where would you draw a line between using setters, which have side-effects of changing values of logically related properties, and using methods, which couldn't be much more descriptive anyway. For example, defining a method called SetDurationTo(Duration duration)
also doesn't reflect that either Start or Finish will be changed.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
一种选择是使您的类不可变,并让方法创建并返回已更改所有适当值的类的新实例。这样就不会有副作用或设置器。想想像
DateTime
这样的东西,您可以调用诸如AddDays
和AddHours
之类的东西,它们将返回一个新的DateTime
实例更改已应用。One option is to make your class immutable and have methods create and return new instances of the class which have all appropriate values changed. Then there are no side effects or setters. Think of something like
DateTime
where you can call things likeAddDays
andAddHours
which will return a newDateTime
instance with the change applied.我一直遵循的一般规则是不允许在没有副作用的属性上使用
public
setter,因为公共 setter 的调用者无法确定可能会发生什么,但是当然,人们修改程序集本身应该有一个很好的主意,因为他们可以看到代码。当然,有时为了可读性、使对象模型合乎逻辑,或者只是为了让事情正常工作,您必须打破规则。就像你说的,总的来说,这确实是一个偏好问题。
I have always worked with the general rule of not allowing
public
setters on properties that are not side-effect free since callers of your public setters can't be certain of what might happen, but of course, people that modify the assembly itself should have a pretty good idea as they can see the code.Of course, there are always times where you have to break the rule for the sake of either readability, to make your object model logical, or just to make things work right. Like you said, really a matter of preference in general.
我认为这主要是一个常识问题。
在这个特定的示例中,我的问题不在于您拥有调整“相关”属性的属性,而是您拥有采用字符串值的属性,然后将其内部解析为 DateTime (或其他)值。
我更愿意看到这样的事情:
也就是说,您明确指出您正在解析字符串。也允许程序员在适当的时候指定强类型对象。
I think it's mostly a matter of common-sense.
In this particular example, my problem is not so much that you've got properties that adjust "related" properties, it's that you've got properties taking string values that you're then internaly parsing into DateTime (or whatever) values.
I would much rather see something like this:
That is, you explicity note that you're doing parsing of strings. Allow the programmer to specify strong-typed objects when that is appropriate as well.
我认为您误解了“副作用”一词,因为它适用于程序设计。设置属性是副作用,无论它改变多少内部状态,只要它改变某种状态。 “无副作用设置器”不会很有用。
副作用是您希望在属性 getter 上避免的事情。读取属性的值是调用者不希望更改任何状态(即导致副作用)的事情,因此如果这样做,通常是错误的或至少是有问题的(有例外,例如延迟加载)。但 getter 和 setter 无论如何都只是方法的包装。对于 CLR 而言,
Duration
属性只是set_Duration
方法的语法糖。这正是类之类的抽象的目的——提供粗粒度的操作,同时保持一致的内部状态。如果您故意尝试避免在单个属性分配中产生多个副作用,那么您的类最终只不过是愚蠢的数据容器。
所以,直接回答这个问题:我在哪里划清界限?任何地方,只要方法/属性实际上执行其名称所暗示的功能即可。如果设置
Duration
也更改了ActivityName
,则可能会出现问题。如果它改变了Finish
属性,那应该是显而易见的; 应该不可能更改Duration
并且Start
和Finish
保持不变。 OOP 的基本前提是对象足够智能,可以自行管理这些操作。如果这在概念层面上困扰您,那么根本没有变异器属性 - 使用具有只读属性的不可变数据结构,其中所有必要的参数都在构造函数中提供。然后有两个重载,一个重载需要
Start
/Duration
,另一个重载需要Start
/Finish
。或者仅将其中一个属性设为可写 - 假设Finish
使其与Start
保持一致 - 然后将Duration
设为只读。使用可变和不可变属性的适当组合来确保只有一种方法可以更改特定状态。否则的话,不用太担心这个。属性(和方法)不应该有意外或未记录副作用,但这是我会使用的唯一准则。
I think you're misunderstanding the term "side-effect" as it applies to program design. Setting a property is a side effect, no matter how much or how little internal state it changes, as long as it changes some sort of state. A "side-effect-free setter" would not be very useful.
Side-effects are something you want to avoid on property getters. Reading the value of a property is something that the caller does not expect to change any state (i.e. cause side-effects), so if it does, it's usually wrong or at least questionable (there are exceptions, such as lazy loading). But getters and setters alike are just wrappers for methods anyway. The
Duration
property, as far as the CLR is concerned, is just syntactic sugar for aset_Duration
method.This is exactly what abstractions such as classes are meant for - providing coarse-grained operations while keeping a consistent internal state. If you deliberately try to avoid having multiple side-effects in a single property assignment then your classes end up being not much more than dumb data containers.
So, answering the question directly: Where do I draw the line? Nowhere, as long as the method/property actually does what its name implies. If setting the
Duration
also changed theActivityName
, that might be a problem. If it changes theFinish
property, that ought to be obvious; it should be impossible to change theDuration
and have both theStart
andFinish
stay the same. The basic premise of OOP is that objects are intelligent enough to manage these operations by themselves.If this bothers you at a conceptual level then don't have mutator properties at all - use an immutable data structure with read-only properties where all of the necessary arguments are supplied in the constructor. Then have two overloads, one that takes a
Start
/Duration
and another that takes aStart
/Finish
. Or make only one of the properties writable - let's sayFinish
to keep it consistent withStart
- and then makeDuration
read-only. Use the appropriate combination of mutable and immutable properties to ensure that there is only one way to change a certain state.Otherwise, don't worry so much about this. Properties (and methods) shouldn't have unintended or undocumented side effects, but that's about the only guideline I would use.
就我个人而言,我认为有副作用来保持一致的状态是有意义的。就像你说的,改变逻辑相关的值是有意义的。从某种意义上说,副作用是可以预料的。但重要的是要明确这一点。也就是说,很明显该方法正在执行的任务具有某种副作用。因此,您可以调用函数
ChangeDurationTo
,而不是SetDurationTo
,这意味着正在发生其他事情。您还可以使用另一种方法来调整持续时间AdjustDurationTo
并传入delta
值。如果您将该函数记录为具有副作用,将会有所帮助。我认为另一种看待它的方法是看看是否会产生副作用。在您的矩形示例中,我希望它更改
top
或bottom
的值以保持内部一致的状态。我不知道这是否是主观的;这对我来说似乎很有意义。一如既往,我认为文档胜出。如果有副作用,请详细记录。最好通过方法名称和支持文档。Personally, I think it makes sense to have a side-effect to maintain a consistent state. Like you said, it makes sense to change logically-related values. In a sense, the side-effect is expected. But the important thing is to make that point clear. That is, it should be evident that the task the method is performing has some sort of side-effect. So instead of
SetDurationTo
you could call your functionChangeDurationTo
, which implies something else is going on. You could also do this another way by having a function/method that adjusts the durationAdjustDurationTo
and pass in adelta
value. It would help if you document the function as having a side-effect.I think another way to look at it is to see if a side-effect is expected. In your example of a Rectangle, I would expect it to change the values of
top
orbottom
to maintain an internally-consistent state. I don't know if this is subjective; it just seems to make sense to me. As always, I think documentation wins out. If there is a side-effect, document it really well. Preferably by the name of the method and through supporting documentation.