C#/面向对象设计 - 维护有效的对象状态

时间:2022-08-25 19:17:39

When designing a class, should logic to maintain valid state be incorporated in the class or outside of it ? That is, should properties throw exceptions on invalid states (i.e. value out of range, etc.), or should this validation be performed when the instance of the class is being constructed/modified ?

在设计一个类时,应该将逻辑维护有效状态合并到类中还是外部?也就是说,属性是否应该在无效状态上抛出异常(即值超出范围等),还是应该在构造/修改类的实例时执行此验证?

8 个解决方案

#1


It belongs in the class. Nothing but the class itself (and any helpers it delegates to) should know, or be concerned with, the rules that determine valid or invalid state.

它属于班级。除了类本身(以及它委派给它的任何帮助者)之外,应该知道或关注确定有效或无效状态的规则。

#2


Yes, properties should check on valid/invalid values when being set. That's what it's for.

是的,属性应在设置时检查有效/无效值。这就是它的用途。

#3


It should be impossible to put a class into an invalid state, regardless of the code outside it. That should make it clear.

无论外面的代码如何,都应该无法将类置于无效状态。这应该说清楚。

On the other hand, the code outside it is still responsible for using the class correctly, so frequently it will make sense to check twice. The class's methods may throw an ArgumentException if passed something they don't like, and the calling code should ensure that this doesn't happen by having the right logic in place to validate input, etc.

另一方面,它之外的代码仍然负责正确使用类,因此经常检查两次是有意义的。如果传递了他们不喜欢的东西,那么类的方法可能抛出ArgumentException,并且调用代码应该确保通过使用正确的逻辑来验证输入等不会发生这种情况。

There are also more complex cases where there are different "levels" of client involved in a system. An example is an OS - an application runs in "User mode" and ought to be incapable of putting the OS into an invalid state. But a driver runs in "Kernel mode" and is perfectly capable of corrupting the OS state, because it is part of a team that is responsible for implementing the services used by the applications.

还有更复杂的情况,系统中涉及客户的不同“级别”。一个例子是操作系统 - 一个应用程序以“用户模式”运行,并且应该无法将操作系统置于无效状态。但是驱动程序以“内核模式”运行并且完全能够破坏操作系统状态,因为它是负责实现应用程序使用的服务的团队的一部分。

This kind of dual-level arrangement can occur in object models; there can be "exterior" clients of the model that only see valid states, and "interior" clients (plug-ins, extensions, add-ons) which have to be able to see what would otherwise be regarded as "invalid" states, because they have a role to play in implementing state transitions. The definition of invalid/valid is different depending on the role being played by the client.

这种双层布置可以在对象模型中发生;模型的“外部”客户端只能看到有效状态,而“内部”客户端(插件,扩展,附加组件)必须能够看到否则将被视为“无效”状态,因为他们在实施状态转换中可以发挥作用。无效/有效的定义根据客户端所扮演的角色而有所不同。

#4


Generally this belongs in the class itself, but to some extent it has to also depend on your definition of 'valid'. For example, consider the System.IO.FileInfo class. Is it valid if it refers to file that no longer exists? How would it know?

一般来说,这属于类本身,但在某种程度上,它还必须依赖于您对“有效”的定义。例如,考虑System.IO.FileInfo类。如果引用不再存在的文件,它是否有效?怎么会知道的?

#5


I would agree with @Joel. Typcially this would be found in the class. However, I would not have the property accessors implement the validation logic. Rather I'd recommend a validation method for the persistence layer to call when the object is being persisted. This allows you to localize the validation logic in a single place and make different choices for valid/invalid based on the persistence operation being performed. If, for example, you are planning to delete an object from the database, do you care that some of its properties are invalid? Probably not -- as long as the ID and row versions are the same as those in the database, you just go ahead and delete it. Likewise, you may have different rules for inserts and updates, e.g., some fields may be null on insert, but required on update.

我同意@Joel。典型地,这可以在课堂上找到。但是,我不会让属性访问器实现验证逻辑。相反,我建议在持久化对象时调用持久层的验证方法。这允许您在单个位置本地化验证逻辑,并根据正在执行的持久性操作对有效/无效做出不同的选择。例如,如果您计划从数据库中删除对象,您是否关心它的某些属性是无效的?可能不是 - 只要ID和行版本与数据库中的版本相同,您就可以继续删除它。同样,您可能对插入和更新有不同的规则,例如,某些字段在插入时可能为null,但在更新时需要。

#6


It depends.

If the validation is simple, and can be checked using only information contained in the class, then most of the time it's worth while to add the state checks to the class.

如果验证很简单,并且只能使用类中包含的信息进行检查,那么大多数时候将状态检查添加到类中是值得的。

There are sometimes, however, where it's not really possible or desirable to do so.

然而,有时候这样做并不可能或不可取。

A great example is a compiler. Checking the state of abstract syntax trees (ASTs) to make sure a program is valid is usually not done by either property setters or constructors. Instead, the validation is usually done by a tree visitor, or a series of mutually recursive methods in some sort of "semantic analysis class". In either case, however, properties are validated long after their values are set.

一个很好的例子是编译器。检查抽象语法树(AST)的状态以确保程序有效通常不是由属性设置器或构造器完成的。相反,验证通常由树访问者或某种“语义分析类”中的一系列相互递归方法完成。但是,在任何一种情况下,都会在设置值后很久验证属性。

Also, with objects used to old UI state it's usually a bad idea (from a usability perspective) to throw exceptions when invalid values are set. This is particularly true for apps that use WPF data binding. In that case you want to display some sort of modeless feedback to the customer rather than throwing an exception.

此外,对于用于旧UI状态的对象,在设置无效值时抛出异常通常是一个坏主意(从可用性的角度来看)。对于使用WPF数据绑定的应用程序尤其如此。在这种情况下,您希望向客户显示某种无模式反馈,而不是抛出异常。

#7


The class really should maintain valid values. It shouldn't matter if these are entered through the constructor or through properties. Both should reject invalid values. If both a constructor parameter and a property require the same validation, you can either use a common private method to validate the value for both the property and the constructor or you can do the validation in the property and use the property inside your constructor when setting the local variables. I would recommend using a common validation method, personally.

该类确实应该保持有效的值。如果通过构造函数或通过属性输入它们并不重要。两者都应该拒绝无效值。如果构造函数参数和属性都需要相同的验证,您可以使用公共私有方法来验证属性和构造函数的值,也可以在属性中进行验证,并在设置时使用构造函数内的属性局部变量。我建议亲自使用通用的验证方法。

Your class should throw an exception if it receives invalid values. All in all, good design can help reduce the chances of this happening.

如果接收到无效值,您的类应抛出异常。总而言之,良好的设计可以帮助减少这种情况发生的可能性。

#8


The valid state in a class is best express with the concept of class invariant. It is a boolean expression which must hold true for the objects of that class to be valid.

类中的有效状态最好用类不变的概念表达。它是一个布尔表达式,必须适用于该类的对象才有效。

The Design by Contract approach suggests that you, as a developer of class C, should guarantee that the class invariant holds:

“按合同设计”方法表明,作为C类开发人员,您应该保证类不变量成立:

  • After construction
  • After a call to a public method
  • 在调用公共方法之后

This will imply that, since the object is encapsulated (noone can modify it except via calls to public methods), the invariant will also be satisfied at entering any public method, or at entering the destructor (in languages with destructors), if any.

这意味着,由于对象是封装的(除了通过调用公共方法之外没有人可以修改它),因此在输入任何公共方法或输入析构函数(使用析构函数的语言)时也会满足不变量(如果有的话)。

Each public method states preconditions that the caller must satisfy, and postconditions that will be satisfied by the class at the end of every public method. Violating a precondition effectively violates the contract of the class, so that it can still be correct but it doesn't have to behave in any particular way, nor maintain the invariant, if it is called with a precondition violation. A class that fulfills its contract in the absence of caller violations can be said to be correct.

每个公共方法都声明了调用者必须满足的前提条件,以及每个公共方法结束时类将满足的后置条件。违反前提条件有效地违反了类的合同,因此它仍然可以是正确的,但它不必以任何特定方式行事,也不必保持不变量,如果它是在违反前提条件的情况下调用的。在没有来电者违规的情况下履行合同的类可以说是正确的。

A concept different from correct but complementary to it (and certainly belonging to the multiple factors of software quality) is that of robust. In our context, a robust class will detect when one of its methods is called without fulfilling the method preconditions. In such cases, an assertion violation exception will typically be thrown, so that the caller knows that he blew it.

一个不同于正确但与之互补的概念(当然属于软件质量的多个因素)是一个强大的概念。在我们的上下文中,一个健壮的类将检测何时调用其方法之一而不满足方法前置条件。在这种情况下,通常会抛出断言违例异常,以便调用者知道他吹了它。

So, answering your question, both the class and its caller have obligations as part of the class contract. A robust class will detect contract violations and spit. A correct caller will not violate the contract.

因此,回答您的问题,班级及其来电者都有义务作为班级合同的一部分。一个强大的类将检测合同违规和吐痰。正确的来电者不会违反合同。

Classes belonging to the public interface of a code library should be compiled as robust, while inner classes could be tested as robust but then run in the released product as just correct, without the precondition checks on. This depends on a number of things and was discussed elsewhere.

属于代码库公共接口的类应该被编译为健壮的,而内部类可以被测试为健壮,但是在发布的产品中运行正确,没有前置条件检查。这取决于许多事情,并在其他地方讨论过。

#1


It belongs in the class. Nothing but the class itself (and any helpers it delegates to) should know, or be concerned with, the rules that determine valid or invalid state.

它属于班级。除了类本身(以及它委派给它的任何帮助者)之外,应该知道或关注确定有效或无效状态的规则。

#2


Yes, properties should check on valid/invalid values when being set. That's what it's for.

是的,属性应在设置时检查有效/无效值。这就是它的用途。

#3


It should be impossible to put a class into an invalid state, regardless of the code outside it. That should make it clear.

无论外面的代码如何,都应该无法将类置于无效状态。这应该说清楚。

On the other hand, the code outside it is still responsible for using the class correctly, so frequently it will make sense to check twice. The class's methods may throw an ArgumentException if passed something they don't like, and the calling code should ensure that this doesn't happen by having the right logic in place to validate input, etc.

另一方面,它之外的代码仍然负责正确使用类,因此经常检查两次是有意义的。如果传递了他们不喜欢的东西,那么类的方法可能抛出ArgumentException,并且调用代码应该确保通过使用正确的逻辑来验证输入等不会发生这种情况。

There are also more complex cases where there are different "levels" of client involved in a system. An example is an OS - an application runs in "User mode" and ought to be incapable of putting the OS into an invalid state. But a driver runs in "Kernel mode" and is perfectly capable of corrupting the OS state, because it is part of a team that is responsible for implementing the services used by the applications.

还有更复杂的情况,系统中涉及客户的不同“级别”。一个例子是操作系统 - 一个应用程序以“用户模式”运行,并且应该无法将操作系统置于无效状态。但是驱动程序以“内核模式”运行并且完全能够破坏操作系统状态,因为它是负责实现应用程序使用的服务的团队的一部分。

This kind of dual-level arrangement can occur in object models; there can be "exterior" clients of the model that only see valid states, and "interior" clients (plug-ins, extensions, add-ons) which have to be able to see what would otherwise be regarded as "invalid" states, because they have a role to play in implementing state transitions. The definition of invalid/valid is different depending on the role being played by the client.

这种双层布置可以在对象模型中发生;模型的“外部”客户端只能看到有效状态,而“内部”客户端(插件,扩展,附加组件)必须能够看到否则将被视为“无效”状态,因为他们在实施状态转换中可以发挥作用。无效/有效的定义根据客户端所扮演的角色而有所不同。

#4


Generally this belongs in the class itself, but to some extent it has to also depend on your definition of 'valid'. For example, consider the System.IO.FileInfo class. Is it valid if it refers to file that no longer exists? How would it know?

一般来说,这属于类本身,但在某种程度上,它还必须依赖于您对“有效”的定义。例如,考虑System.IO.FileInfo类。如果引用不再存在的文件,它是否有效?怎么会知道的?

#5


I would agree with @Joel. Typcially this would be found in the class. However, I would not have the property accessors implement the validation logic. Rather I'd recommend a validation method for the persistence layer to call when the object is being persisted. This allows you to localize the validation logic in a single place and make different choices for valid/invalid based on the persistence operation being performed. If, for example, you are planning to delete an object from the database, do you care that some of its properties are invalid? Probably not -- as long as the ID and row versions are the same as those in the database, you just go ahead and delete it. Likewise, you may have different rules for inserts and updates, e.g., some fields may be null on insert, but required on update.

我同意@Joel。典型地,这可以在课堂上找到。但是,我不会让属性访问器实现验证逻辑。相反,我建议在持久化对象时调用持久层的验证方法。这允许您在单个位置本地化验证逻辑,并根据正在执行的持久性操作对有效/无效做出不同的选择。例如,如果您计划从数据库中删除对象,您是否关心它的某些属性是无效的?可能不是 - 只要ID和行版本与数据库中的版本相同,您就可以继续删除它。同样,您可能对插入和更新有不同的规则,例如,某些字段在插入时可能为null,但在更新时需要。

#6


It depends.

If the validation is simple, and can be checked using only information contained in the class, then most of the time it's worth while to add the state checks to the class.

如果验证很简单,并且只能使用类中包含的信息进行检查,那么大多数时候将状态检查添加到类中是值得的。

There are sometimes, however, where it's not really possible or desirable to do so.

然而,有时候这样做并不可能或不可取。

A great example is a compiler. Checking the state of abstract syntax trees (ASTs) to make sure a program is valid is usually not done by either property setters or constructors. Instead, the validation is usually done by a tree visitor, or a series of mutually recursive methods in some sort of "semantic analysis class". In either case, however, properties are validated long after their values are set.

一个很好的例子是编译器。检查抽象语法树(AST)的状态以确保程序有效通常不是由属性设置器或构造器完成的。相反,验证通常由树访问者或某种“语义分析类”中的一系列相互递归方法完成。但是,在任何一种情况下,都会在设置值后很久验证属性。

Also, with objects used to old UI state it's usually a bad idea (from a usability perspective) to throw exceptions when invalid values are set. This is particularly true for apps that use WPF data binding. In that case you want to display some sort of modeless feedback to the customer rather than throwing an exception.

此外,对于用于旧UI状态的对象,在设置无效值时抛出异常通常是一个坏主意(从可用性的角度来看)。对于使用WPF数据绑定的应用程序尤其如此。在这种情况下,您希望向客户显示某种无模式反馈,而不是抛出异常。

#7


The class really should maintain valid values. It shouldn't matter if these are entered through the constructor or through properties. Both should reject invalid values. If both a constructor parameter and a property require the same validation, you can either use a common private method to validate the value for both the property and the constructor or you can do the validation in the property and use the property inside your constructor when setting the local variables. I would recommend using a common validation method, personally.

该类确实应该保持有效的值。如果通过构造函数或通过属性输入它们并不重要。两者都应该拒绝无效值。如果构造函数参数和属性都需要相同的验证,您可以使用公共私有方法来验证属性和构造函数的值,也可以在属性中进行验证,并在设置时使用构造函数内的属性局部变量。我建议亲自使用通用的验证方法。

Your class should throw an exception if it receives invalid values. All in all, good design can help reduce the chances of this happening.

如果接收到无效值,您的类应抛出异常。总而言之,良好的设计可以帮助减少这种情况发生的可能性。

#8


The valid state in a class is best express with the concept of class invariant. It is a boolean expression which must hold true for the objects of that class to be valid.

类中的有效状态最好用类不变的概念表达。它是一个布尔表达式,必须适用于该类的对象才有效。

The Design by Contract approach suggests that you, as a developer of class C, should guarantee that the class invariant holds:

“按合同设计”方法表明,作为C类开发人员,您应该保证类不变量成立:

  • After construction
  • After a call to a public method
  • 在调用公共方法之后

This will imply that, since the object is encapsulated (noone can modify it except via calls to public methods), the invariant will also be satisfied at entering any public method, or at entering the destructor (in languages with destructors), if any.

这意味着,由于对象是封装的(除了通过调用公共方法之外没有人可以修改它),因此在输入任何公共方法或输入析构函数(使用析构函数的语言)时也会满足不变量(如果有的话)。

Each public method states preconditions that the caller must satisfy, and postconditions that will be satisfied by the class at the end of every public method. Violating a precondition effectively violates the contract of the class, so that it can still be correct but it doesn't have to behave in any particular way, nor maintain the invariant, if it is called with a precondition violation. A class that fulfills its contract in the absence of caller violations can be said to be correct.

每个公共方法都声明了调用者必须满足的前提条件,以及每个公共方法结束时类将满足的后置条件。违反前提条件有效地违反了类的合同,因此它仍然可以是正确的,但它不必以任何特定方式行事,也不必保持不变量,如果它是在违反前提条件的情况下调用的。在没有来电者违规的情况下履行合同的类可以说是正确的。

A concept different from correct but complementary to it (and certainly belonging to the multiple factors of software quality) is that of robust. In our context, a robust class will detect when one of its methods is called without fulfilling the method preconditions. In such cases, an assertion violation exception will typically be thrown, so that the caller knows that he blew it.

一个不同于正确但与之互补的概念(当然属于软件质量的多个因素)是一个强大的概念。在我们的上下文中,一个健壮的类将检测何时调用其方法之一而不满足方法前置条件。在这种情况下,通常会抛出断言违例异常,以便调用者知道他吹了它。

So, answering your question, both the class and its caller have obligations as part of the class contract. A robust class will detect contract violations and spit. A correct caller will not violate the contract.

因此,回答您的问题,班级及其来电者都有义务作为班级合同的一部分。一个强大的类将检测合同违规和吐痰。正确的来电者不会违反合同。

Classes belonging to the public interface of a code library should be compiled as robust, while inner classes could be tested as robust but then run in the released product as just correct, without the precondition checks on. This depends on a number of things and was discussed elsewhere.

属于代码库公共接口的类应该被编译为健壮的,而内部类可以被测试为健壮,但是在发布的产品中运行正确,没有前置条件检查。这取决于许多事情,并在其他地方讨论过。