在属性中实现逻辑是一种好的做法吗?

34

我们使用ASP.NET和C#,基于我通过的开源项目/文章,我发现许多属性中包含逻辑。但是当我这样做时,团队领导告诉我,在属性中放置逻辑并不好,而是要通过方法调用逻辑...

那真的很糟糕吗?为什么不在属性中使用逻辑?

谢谢。


实际上,团队领导在谈论不要将逻辑放在getter部分!因此,在任务审查期间,他重构了我的属性“提取方法”。 - Jawad Al Shaikh
8个回答

41

属性访问应该是即时的(没有长时间等待),一致的(没有改变的值)和安全的(没有异常)。如果您能保证这些条件,我认为在属性中放置逻辑是可以的。


2
你所说的“consistent”是指idempotent吗? - Robert Harvey
14
如果某人试图设置非法值,那么在setter上使用异常是可以的。 - Thorarin
幂等性应该适用。我同意在设置器上使用异常,但我认为你绝对不应该在获取器上抛出异常。 - Instance Hunter
当然,一致性也有例外。例如,像 DateTime.Now 和 Stopwatch.Elapsed 这样的东西。 - Instance Hunter

33

在属性中加入一些逻辑通常是可以的。例如,在setter中进行参数验证和在getter中进行惰性计算都是相当常见的。

然而,像数据库调用这样的昂贵操作在属性访问中通常不是一个好主意。开发人员倾向于假设属性的评估成本是相当低廉的。

最终需要做出判断 - 但我肯定拒绝了建议,即属性只应该是微不足道的,以至于它们可以使用自动属性来实现。


我同意这个答案,但是对于数据驱动的情况呢?比如一个 Product 有一个 Supplier,而一个 Supplier 又有一系列的 Products。在对象构建期间应该加载这些关联实体吗?还是在首次访问属性时?又或者由于这两种选择都存在问题,它们不应该通过属性进行访问? - Anthony Pegram
@Anthony -- 我认为将属性加载到对象上不是对象本身的责任,而是您的数据访问层或ORM的责任。 - tvanfosson
@Anthony:这就是我为什么用“通常”这个词的原因 :) 诸如 Linq to Sql 的东西可能会做这种事情——但我希望开发人员知道他们正在处理 ORM 绑定对象。虽然我不希望在所有地方都看到这种情况。 - Jon Skeet

7
属性就是方法,它们只是getter/setter的快捷方式。任何在getter/setter中有效的逻辑都可以放在属性中。任何通常不会放在getter/setter中的逻辑都不应该放在属性中。一般来说,如果您(作为类的消费者)不能合理地期望设置属性值,甚至更糟的是,获取属性值可能会导致某种行为发生,那么该逻辑可能属于其他地方。换句话说,逻辑应与获取或设置属性相关且一致。
引用上面链接文章中的话:
“属性是提供灵活机制以读取、写入或计算私有字段值的成员。属性可以像公共数据成员一样使用,但它们实际上是称为访问器的特殊方法。这使得数据可以轻松访问,同时仍提供方法的安全性和灵活性。”

1
一个getter/setter中哪种逻辑是有效的? - Robert Harvey
1
@Robert - 很难给出全面的定义,但它应该通过实际检验。例如,合理地期望更改订单的价格或数量会改变其成本。您不会期望它会更改交付订单的供应商。 - tvanfosson
原则上同意,尽管我写过一些从文本文件中读取数据的类,在设置特定属性时会将文件倒回到开头(因为否则设置该特定属性会使文件阅读器处于无效状态)。 - Robert Harvey

3

这里有一个常见的答案:要看情况。

通常,在getter和setter方法中实现业务逻辑并不是一个好主意。如果你的对象只是一个简单的数据传输对象(DTO),那么这将违反单一职责原则。

然而,状态跟踪逻辑和其他维护工作通常会在属性中找到。例如,Entity Framework 4自跟踪实体在每个基本属性的setter中都有状态管理逻辑,以便进行跟踪。

属性中逻辑的替代方案是面向切面编程(AOP)。使用AOP,您可以在对象和托管进程之间“注入”逻辑。可以“拦截”对对象的访问并有条件地处理。


同意,你总结了我想的东西。 :) 在我的小项目中,我在一个"Player"类的属性中加入了一些逻辑,用于设置玩家在游戏中的所属队伍。它首先检查当前玩家所在的队伍和新的value值是否相同,然后发送一个控制台命令来改变玩家的队伍为value值。 - Zack

2
将业务逻辑放在setter中可能会导致问题,如果您需要使用JSon、XML或ORM对对象进行序列化/反序列化,则更容易出现问题。例如,在使用文档数据库或ORM时可能会出现此类问题。其中一些(例如NHibernate)可以配置为访问后备字段而不是setter。
我认为使用公共Getter和私有setter以及一个方法来设置值并根据需要添加其他逻辑是一个不错的方法。大多数序列化程序可以访问私有setter,因此您最终得到的是持久化对象的准确表示,而不会意外触发可能在反序列化时错误地更改值的逻辑。
但是,如果您认为永远不需要序列化/反序列化,则这不应该成为问题。

0
在我看来,这是完全可以的。我认为,将属性作为语言特性的唯一理由就是你可以在其中放置逻辑。否则,你可能会直接访问底层数据成员。

1
我一直认为访问修饰符是另一个原因,明确声明将逻辑嵌入其中...显然要记住类的上下文... - L.Trabacchin

-1
通常情况下,一个属性仅影响一个变量,因为它主要是为此目的而创建的。但有时,您可能想要一个更高级别的属性,而不仅仅是一个一对一的变量。所以,在这种情况下,它包含代码是正常的。但是请记住,属性并不像函数一样用来调用。当您调用一个函数时,您知道它会执行一些处理。当您调用一个属性时,您期望它快速运行。
最后,这是一个偏好问题,就像编码规范一样,遵循您上级的建议取决于您自己的判断。所以,并没有什么不好或好,这取决于您自己的判断。

-1
在我看来,业务逻辑只允许在Setter/Getter中的特定情况下存在。例如:可以放置负责验证输入的逻辑,因为Setter负责维护对象状态,所以该状态不应被违反。因此,您应该将业务逻辑剪切到仅负责一个主题的最小代码部分。
另一件事是,您的类应该是(在最佳情况下)POCO(Plain Old CLR Object)。为什么呢?因为它应该是可重用的,当类在属性中包含逻辑时,可重用性可能会被简单地阻止。想象一下,如果您有一个带有某些SQLServer验证的SqlServerPerson,那么当您更改ORM/DB访问时,将其替换为例如NHibernatePerson可能会很困难。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接