在设计一个类时,保持有效状态的逻辑应该包含在类中还是之外?也就是说,属性是否应该在无效状态下抛出异常(即值超出范围等),还是应该在构造/修改类的实例时执行此验证?
8 回答
它属于类。除了类本身(以及它所委托的任何助手)之外,没有任何东西应该知道或关心确定有效或无效状态的规则。
是的,属性应在设置时检查有效/无效值。这就是它的用途。
将一个类置于无效状态应该是不可能的,不管它外面的代码是什么。这应该说清楚。
另一方面,它外部的代码仍然负责正确使用类,所以经常检查两次是有意义的。类的方法可能会抛出ArgumentException
他们不喜欢的 if 传递,并且调用代码应该通过正确的逻辑来验证输入等来确保不会发生这种情况。
还有更复杂的情况,系统中涉及不同“级别”的客户端。一个例子是操作系统 - 应用程序在“用户模式”下运行,并且应该无法将操作系统置于无效状态。但是驱动程序在“内核模式”下运行并且完全能够破坏操作系统状态,因为它是负责实现应用程序使用的服务的团队的一部分。
这种双层排列可以出现在对象模型中;模型的“外部”客户端可能只看到有效状态,而“内部”客户端(插件、扩展、附加组件)必须能够看到否则会被视为“无效”状态的内容,因为它们在实现状态转换方面可以发挥作用。无效/有效的定义根据客户端所扮演的角色而有所不同。
通常这属于类本身,但在某种程度上它还必须取决于您对“有效”的定义。例如,考虑System.IO.FileInfo
类。如果它引用不再存在的文件是否有效?它怎么会知道?
我同意@Joel。通常这会在课堂上找到。但是,我不会让属性访问器实现验证逻辑。相反,我建议在持久化对象时调用持久层的验证方法。这允许您将验证逻辑本地化在一个地方,并根据正在执行的持久性操作对有效/无效做出不同的选择。例如,如果您计划从数据库中删除一个对象,您是否关心它的某些属性是无效的?可能不会——只要 ID 和行版本与数据库中的相同,您就可以继续删除它。同样,您可能对插入和更新有不同的规则,例如,某些字段在插入时可能为空,但在更新时需要。
这取决于。
如果验证很简单,并且可以仅使用类中包含的信息进行检查,那么大多数时候值得将状态检查添加到类中。
但是,有时确实不可能或不希望这样做。
一个很好的例子是编译器。检查抽象语法树 (AST) 的状态以确保程序有效通常不是由属性设置器或构造器完成的。相反,验证通常由树访问者或某种“语义分析类”中的一系列相互递归方法完成。然而,在任何一种情况下,属性都会在设置它们的值之后很长时间才被验证。
此外,对于用于旧 UI 状态的对象,在设置无效值时抛出异常通常是一个坏主意(从可用性的角度来看)。对于使用 WPF 数据绑定的应用程序尤其如此。在这种情况下,您希望向客户显示某种无模式的反馈,而不是抛出异常。
类中的有效状态最好用类不变量的概念来表达。这是一个布尔表达式,必须为该类的对象有效。
契约式设计方法建议您作为 C 类的开发人员,应保证类不变量成立:
- 施工后
- 调用公共方法后
这意味着,由于对象是封装的(没有人可以修改它,除非通过调用公共方法),在进入任何公共方法或进入析构函数(在具有析构函数的语言中)(如果有)时,也将满足不变量。
每个公共方法都声明调用者必须满足的前置条件,以及每个公共方法结束时类将满足的后置条件。违反先决条件实际上违反了类的契约,因此它仍然可以是正确的,但它不必以任何特定方式表现,也不必保持不变量,如果它被调用时违反先决条件。在没有调用者违规的情况下履行其契约的类可以说是正确的。
一个不同于正确但与之互补的概念(当然属于软件质量的多个因素)是健壮的。在我们的上下文中,健壮的类将检测何时调用其方法之一而不满足方法先决条件。在这种情况下,通常会抛出断言冲突异常,以便调用者知道他搞砸了。
所以,回答你的问题,类和它的调用者都有义务作为类合同的一部分。一个健壮的类将检测合同违规并吐出。正确的调用者不会违反合同。
属于代码库的公共接口的类应该被编译为健壮的,而内部类可以被测试为健壮的,但随后在发布的产品中以正确的方式运行,而无需进行前置条件检查。这取决于许多事情,并在别处进行了讨论。
该类确实应该保持有效值。这些是通过构造函数还是通过属性输入的,这无关紧要。两者都应该拒绝无效值。如果构造函数参数和属性都需要相同的验证,您可以使用公共私有方法来验证属性和构造函数的值,或者您可以在属性中进行验证并在设置时使用构造函数中的属性局部变量。我个人建议使用通用的验证方法。
如果接收到无效值,您的类应该抛出异常。总而言之,好的设计可以帮助减少这种情况发生的机会。