【问题标题】:C# / Object oriented design - maintaining valid object stateC# / 面向对象的设计 - 维护有效的对象状态
【发布时间】:2010-11-10 11:26:12
【问题描述】:

在设计一个类时,保持有效状态的逻辑应该包含在类中还是类之外?也就是说,属性是否应该在无效状态下抛出异常(即值超出范围等),还是应该在构造/修改类的实例时执行此验证?

【问题讨论】:

标签: c# oop state


【解决方案1】:

它属于类。除了类本身(以及它所委托的任何助手)之外,没有任何东西应该知道或关心确定有效或无效状态的规则。

【讨论】:

  • 感谢您的回复。在这种情况下,我会用 try...catch 包围实例化还是“优雅地”失败?例如:尝试 { Type myType = new Type(aValue); } catch (InvalidException e) { .. } 还是设置为一些“安全”的默认值?
  • 没有。那不是优雅的失败,那是谎言。不理会异常,直到您可以对它做一些事情
  • 好的,我想我开始明白了。我假设“安全”默认值是谎言,而不是 try..catch ?在 try..catch 案例中,我没有“吃掉”异常,我只是将处理放在一边并放入 ..
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    应该不可能将一个类置于无效状态,不管它外面的代码是什么。这应该说清楚。

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

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

    这种双层排列可以出现在对象模型中;模型的“外部”客户端可能只看到有效状态,而“内部”客户端(插件、扩展、附加组件)必须能够看到否则会被视为“无效”状态的内容,因为它们在实现状态转换方面可以发挥作用。无效/有效的定义因客户端所扮演的角色而异。

    【讨论】:

    • 那么,如果您有一个验证要求,即具有 Widget 的实体也必须具有 Gadget 会发生什么情况,反之亦然。由于您不能同时分配两者(通常),因此您必须至少在短时间内处于无效状态。对于大多数目的,我认为您不能持久无效状态就足够了。临时对象只要不被存储就可能是无效的。
    • 这是在某种假设的语言中,我无法定义一个需要两个参数的方法吗?
    • @Earwicker -- 请注意,问题标记为 C#,其中类的数据通常通过属性访问器而不是方法设置。您是否认为为了保持有效状态,您需要同时设置所有相关属性的方法?假设您在方法的持续时间内允许一些中间无效状态,即。这似乎是一个相当繁重的要求。为什么不让外部实体以与类方法相同的方式构建有效对象并将验证推迟到对象被持久化?
    • ...我承认在某些情况下您会希望如此严格,但我认为这些情况并不典型。
    • @tvanfosson - 如果可以通过设置属性分阶段修改状态,那么根据定义,从设置属性的代码的角度来看,中间状态是有效的。
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

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

      【讨论】:

      【解决方案6】:

      视情况而定。

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

      然而,有时确实不可能或不希望这样做。

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

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

      【讨论】:

        【解决方案7】:

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

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

        【讨论】:

          【解决方案8】:

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

          Design by Contract 方法建议您作为 C 类的开发人员,应保证类不变量成立:

          • 施工后
          • 调用公共方法后

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

          每个公共方法都声明调用者必须满足的前置条件,以及每个公共方法结束时类将满足的后置条件。违反先决条件实际上违反了类的契约,因此它仍然可以是正确的,但它不必以任何特定方式表现,也不必保持不变量,如果它被调用时违反先决条件。在没有调用者违规的情况下履行其契约的类可以说是正确

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

          因此,回答您的问题,类及其调用者都有义务作为类合同的一部分。一个健壮的类将检测合同违规并吐出。正确的调用者不会违反合同。

          属于代码库的公共接口的类应该被编译为健壮的,而内部类可以被测试为健壮的,但随后在发布的产品中以正确的方式运行,而不需要先决条件检查。这取决于很多事情和was discussed elsewhere

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-01-19
            • 1970-01-01
            相关资源
            最近更新 更多