【问题标题】:Null Object Pattern to avoid Null checks?空对象模式以避免空检查?
【发布时间】:2010-10-17 05:53:27
【问题描述】:

最近我遇到了空对象设计模式,我的同事说它可以用来消除在整个代码中遇到的空指针检查。

例如,假设一个 DAO 类返回有关客户的信息(在一个名为 CustomerVO 的值对象中)。我的主类应该提取 firstName 和 emailId 并向客户发送电子邮件。

...
CustomerVO custVO = CustomerDAO.getCustomer(customerID);
if(custVO != null) { // imp, otherwise we may get null ptr exception in next line
     sendEmail(custVO.getFirstName(), custVO.getEmailID());
}
...

这是一个非常简单的示例,但是根据值对象的复杂性,这种空值检查可以迅速传播到您的代码中。

我有两个关于空值检查的问题, - 他们倾向于使代码丑陋且难以阅读 - 经验不足的开发人员在实际上应该抛出异常时进行了不必要的空检查。例如在上面的代码中,最好从 getCustomer() 本身抛出异常,因为如果它无法找到给定 CustID 的客户信息,则表明 CustID 无效。

好的,回到空对象模式,我们可以使用“空”CustomerVO 对象来隐藏空检查吗?

CustomerVO {
   String firstName = "";
   String emailID = ""; 
}

这不是很有意义。你觉得呢?

您要遵循哪些措施来最大程度地减少应用中的空检查。

【问题讨论】:

    标签: design-patterns null-object-pattern


    【解决方案1】:

    虽然空对象模式有它的用途,但您仍然需要在此处进行检查,否则您将尝试 sendEmail() 到一个空字符串的电子邮件地址(或者您推送检查进入sendEmail(),它可以很容易地检查null)。

    如果CustomerVO 类实现了sendEmail() 方法,那么空对象模式将非常有用。那么您可以简单地将调用链接在一起,因为getCustomer() 的合约将确保不会返回null 引用:

    CustomerDAO.getCustomer(customerID).sendEmail();
    

    在这种情况下,sendEmail() 方法会检查它是否被要求对特殊的“空对象”采取行动,并且什么也不做(或任何适当的事情)。

    【讨论】:

    • +1,虽然我不希望我的客户有 sendEmail 方法。那只是给客户类太多的责任。这就像给每个类一个“打印”方法,现在必须在任何地方实现打印逻辑,或者使每个类都依赖于某个打印管理器,而它可能被限制为打印管理器和实际启动打印的表单/类,给打印管理器要打印的类。空模式非常好,例如在单元测试中提供一个不做任何事情的日志类,因此您不必修改正在测试的代码。
    【解决方案2】:

    在这种情况下,空对象可能不合适,因为默认值实际上可能隐藏了实际上是异常的内容。如果您发现自己必须检查您是否有安全的 null 来执行其他一些活动,那么 null 模式不会给您带来任何好处。

    正如您所说,许多新开发人员花时间试图保护他们的代码免受比停止程序更糟糕的异常情况。

    【讨论】:

      【解决方案3】:

      如果找不到客户,您的 getCustomer 方法是否应该抛出异常,而不是返回 null?

      答案当然取决于:几乎永远不会出现客户 ID 不存在的情况?换句话说,这是一个例外情况吗?如果是这样,例外是适当的。

      但是,在数据访问层中,某些东西不存在通常是很正常的。在这种情况下,最好不要扔,因为这不是意外的例外情况。

      “返回具有空字段的非空对象”可能不会更好。如果不添加一些可能比空检查更糟糕的检查代码,您如何知道返回的对象是否“有效”?

      因此,如果获取的内容不存在可能是正常状态,那么空值检查模式可能是最好的。如果这是意外情况,那么让数据访问方法抛出 NotFound 异常可能会更好。

      【讨论】:

      • 看我的回答,如果不能存在的东西,有一个方法来检查它是否存在。这样你只需要在异常的地方抛出异常。
      【解决方案4】:

      我对这种类型的代码有疑问,这是一种常见的模式。如果你分解你实际在做的事情,你根本不需要空检查。在我看来,这里的问题是您违反了 SRP。

      CustomerVO custVO = CustomerDAO.getCustomer(customerID); 方法做了两件事。

      首先它返回一个客户,如果客户存在,其次返回null,如果没有这样的客户。这是两个不同的操作,应该这样编码。

      更好的方法是:

      bool customerExists = CustomerDAO.exists(customerID);
      
      if (customerExists)
      {
        CustomerVO custVO = CustomerDAO.getCustomer(customerID);
        sendEmail(custVO.getFirstName(), custVO.getEmailID());
      }
      else
      {
         // Do whatever is appropriate if there is no such customer.
      }
      

      }

      因此,将方法拆分为两个,一个检查请求的对象是否存在,第二个实际检索它。不需要任何异常,设计和语义非常清晰(在我看来,它不是不存在返回空模式)。此外,在这种方法中,如果请求的客户不存在,CustomerDAO.getCustomer(customerID) 方法会抛出 ArgumentException。毕竟,您已经要求Customer,但没有。

      此外,在我看来,任何方法都不应该返回nullNull 明确表示,“我不知道正确答案是什么,我没有返回的价值”。 Null 不是意义,而是缺乏意义。问问自己,‘为什么我要退回一些我知道不应该真的发生的事情?您的方法GetCustomer 显然应该返回Customer。如果您返回null,您只是在推回如何备份调用链并违反合同的责任。如果有有意义的默认值,请使用它,但在此处抛出异常可能会让您更难思考正确的设计是什么。

      【讨论】:

      • 只要确保您在此处设计线程安全。对exists() 的初始调用可能会返回true,但是如果其他代码在调用之间删除了客户,则随后对getCustomer() 的调用可能仍会失败。像TryGet() 这样的模式试图缓解这种情况。有关此想法的示例,请参见 docs.microsoft.com/en-us/dotnet/api/…
      【解决方案5】:

      名称是 NULL 对象设计模式,而不是 NULL 检查设计模式。空对象表示没有对象。当您有对象在协作中工作时,应该使用它。在您的情况下,您正在检查 NULL 检查应该没问题的对象的存在。

      NULL 设计模式并不意味着取代 NULL 异常处理。这是 NULL 设计模式的附带好处之一,但其目的是提供默认行为。

      不应将 NULL 检查替换为 NULL 设计模式对象,因为它可能导致应用程序中的静默缺陷。

      请参阅下面的文章,其中详细介绍了 NULL 设计模式的 DO 和 Donts。

      http://www.codeproject.com/Articles/1042674/NULL-Object-Design-Pattern

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-11-20
        • 1970-01-01
        • 2019-12-07
        • 1970-01-01
        • 2013-07-01
        • 1970-01-01
        • 2018-05-11
        相关资源
        最近更新 更多