空对象模式

24

越来越多的人声称你不应该返回null,而应该始终使用Null对象模式。当使用集合/映射/数组或调用布尔函数如isAuthenticated()时,我可以看到NOP的实用性,这里有展示

我还没有找到完全令人信服的资料。请容我整理一下我的想法。

我的理解是,不要返回一个空对象,而是返回一个已经“清零”的有效对象。

因此,例如,客户端将调用以获取一个对象:

Car car = getCar();

如果不使用NOP,您需要在调用任何方法之前检查从getCar()返回的对象是否为null:

if (car != null){
    color = car.getColor();
    doScreenStuff(color);
   }
使用NOP,代替getCar()返回null,它现在返回一个已经被有效“清零”的对象。因此,现在我们不再需要做if (car != null),而是可以直接请求颜色。因此,我认为我们的“清零”对象在调用颜色时将返回“none”。
这有什么帮助呢?似乎在空对象上向前移动并调用方法会引起与检查null一样的痛苦。现在,当显示信息时,我们需要检查颜色是否不是“none”,高度不是0,或者您拥有的其他值。因此,本质上,我们不是在处理开始时检查汽车是否为空,而是在之后检查我们拥有的汽车对象是真正的汽车还是替代品。也就是说,我们不想显示一堆空对象,所以我们需要一些方法来过滤掉所有的空对象。
这种过滤就像调用 if (car != null) 一样是一个额外的步骤。唯一的区别是,通过检查null,我们可以通过抛出异常停止处理,一旦发现汽车对象为null,而使用NOP我们在空对象上调用方法,并继续运行,直到该显示对象的时间到来,此时我们过滤掉空的对象。此外,您需要了解空对象返回的值。例如getColor()是否返回“none”或“empty”。
显然,我肯定忽略了一些东西。提前致谢。
6个回答

16
MattPutnam的回答非常到位,我完全赞同。 我想补充一下:当您分析“null对象”概念时,似乎可以归结为数学概念单子。您可以这样考虑:单子是具有以下两个属性的类型:
  1. 一个“追加”,“求和”或类似操作,需要结合性a.op(b).op(c)等同于a.op(b.op(c))
  2. 作为运算的中性元素恒等元素的“空”,“零”或“null”值。
空对象模式的经典示例是返回空列表或数组而不是null。好吧,列表是单子,附加作为操作,空列表作为中性元素。
现在,您在Car示例中面临的问题是Car实际上不是单子;没有“空汽车”或“中性汽车”的概念,并且没有真正合理的操作可以用来将两个Car组合成一个。
因此,您正确获得的建议是使用像Java 8Optional这样的东西。窍门在于,无论T是什么类型,Optional<T>都是一个单子:
  1. 单子的“组合”操作是“如果第一个值不为空,则选择第一个值,否则选择第二个值”:
    • x || empty = x
    • empty || x = x
  2. 中性元素是Optional.empty(),因为Optional.empty().orElse(anything)anything相同。
因此,Optional<T>基本上是添加空对象到像Car这样没有空对象的类型的包装器。 Optional<T>.orElse(T value)方法是“选取第一个非empty值”的单子的略微修改版本。

10

当null对象有合理的、实际的值时,空对象模式才有意义。其目的不是推迟null,而是通过用仍然可用的实际数据来表示无内容或空白,从而完全消除了null的概念。例如,树结构中的空洞自然情况,如Wikipedia文章中所述

null汽车没有意义。在这种情况下,似乎更合适的做法是使getCar()返回Optional<Car>


3
如果您没有看到这种编程范式的优点,那么它可能不适合您使用。面向对象编程的整个理念是为了让您的事情更简单。不要被困惑在认为您需要采用其他人复杂的基于模式的系统中。通常需要大量工作来学习各种模式并有效地使用它们,因此最好成长而不是强迫自己使用它。
就这种特定的模式而言,它假定一种编程风格,这种风格可能不适合您。我自己从不使用它,因为我会将nulls作为合法值(缺少数据)返回,并且每种情况都处理得不同,因此"集中处理"对我来说毫无意义。当我返回布尔值时,我使用原始数据类型。
总之,如果某种模式对您来说似乎很不自然,请不要使用它。

我认为你是对的。在编程时,我总是过于担心使用错误的模式。在这种情况下,似乎有些人建议如果你返回null,那么你就完全错了:http://www.yegor256.com/2014/05/13/why-null-is-bad.html - TigerBear

2

NULL 设计模式填充了一个对象的缺失行为,应该只在一个对象与另一个对象协作时使用。

NULL 设计模式并不是要替换 NULL 异常处理。虽然它是 NULL 设计模式的一个副产品,但其意图是提供默认行为。

例如,考虑下面的伪代码示例。它是一个简单的客户类,将折扣计算委托给一个折扣对象。

class Customer {
    IDiscount dis = new NormalDiscount();
    public double CalculateDiscount() {
        return dis.Calculate();
    }
    // Code removed for simplicity
}

现在假设我们想要创建拖欠付款的客户。因此,我们从上述类继承,由于很多属性和行为相同,但我们不希望进行折扣计算,因为拖欠付款的客户不符合折扣条件。因此,我们将折扣对象设置为空值,但这是一个问题,因为现在由于空值折扣对象的缘故,折扣计算将会崩溃。
class DefaultedCustomer : Customer {
    IDiscount dis = null;
    public double CalculateDiscount() {
        return dis.Calculate(); <---- This will crash in parent
    }
    // Code removed for simplicity
}

开发人员解决这个问题的一种方式是检查NULL并返回零。如果你闭上眼睛并逻辑思考,实际上这是一个没有为默认客户提供折扣的业务场景。

因此,我们可以创建一个DEFAULT DISCOUNT BEHAVIOR类,如下所示,它返回零折扣,并将其用于默认客户,而不是通过检查NULL来在技术上修复此问题。这比检查NULL更清洁。

public class DefaultDiscount : IDiscount {
    public void Calculate() {
        return 0;
    }
}

NULL值在异常处理中是必需的,但不用于修复协作中对象缺失的问题。如果没有协作,NULL设计模式就没有意义。

由于NULL设计模式,NULL检查得以避免,但这更多地是副作用和附带好处,而不是主要意图。

我从C#中的NULL设计模式中获得了以上所有想法,该文章解释了NULL的Do's和Donts。


0

在我的经验中,空指针对象是为了将空检查限制在一个中心位置以避免空指针异常。

如果许多服务都在使用CarFactory,则更容易让Carfactory处理空结果,而不是让每个单独的服务处理它。此外,它确保每个空结果都以相同的方式处理,无论是什么,什么也不做还是一些指定的逻辑。缺点是,如果没有正确处理,可能会导致一些暂时令人困惑的错误(特别是因为空指针异常会大声喊出来)。

我现在不太使用它了。有替代使用空检查的方法,例如使用Java 8 Optional。对此有赞成和反对的人,这绝不是空对象模式的替代品。

String result = Optional.ofNullable(someInteger)
   .map(Integer::valueOf)
   .map(i -> i + 1)
   .map(Object::toString)
   .orElse("number not present");

System.out.println(result);

1
空对象模式和可选项并不具有相同的目的。可选项用于指示值可能存在或不存在。空对象模式仅编码了可能存在“无内容”的对象,但仍具有有用行为的事实。 - Cubic
抱歉,我可能没有表达清楚,我的意思是空值检查的替代方案是可选项,然后对于空对象模式,“这绝不是空对象模式的替代品”。我已经有一段时间没有使用过空对象模式,因为我最近没有使用案例,但我绝不是在暗示可选项可以取代空对象模式。 - Derek_M

-3

使用 Optional 而不是 Car 类型返回将不会消除检查车对象是否实际存在的步骤。

当返回 car 时,您应该检查 if (car != null)。同样,当访问 Optional 时,您应该检查 if (car.isPresent()) 然后调用 car.get()。

在未检查存在性的情况下执行 get() 是不可接受的,可以使用 checkstyle 配置轻松识别并避免抛出错误。

您可能会问.. 如果我们在这两种模式中都进行了存在性检查,那我们到底因为这个得到了什么优势?

答案在于知道在使用之前必须进行检查。如果您严格遵循返回 Optional 的惯例,无论在哪里它都可以为空,那么你就不需要在非必选项的地方检查 nulls。

它作为一种以程序方式记录内部方法的方式,从而通过 checkstyles 强制执行其检查的方式。


可选项并不等同于空对象模式(https://en.wikipedia.org/wiki/Null_Object_pattern)。 - Rodrigo Ruiz
在您全新的兰博基尼上仅以20英里每小时的速度行驶,就好比只使用 isPresent()get() 来处理 Optional。在这种情况下更好的做法是 getCar().ifPresent(car -> doScreenStuff(car.getColor())) - Michael

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