21

似乎有越来越多的人说您永远不应该返回 null,而应该始终使用 Null 对象模式。在使用集合/映射/数组或调用诸如 isAuthenticated() 之类的布尔函数时,我可以看到 NOP 的用处,此处显示

我在这方面还没有找到任何令人信服的东西。在我尝试整理我的想法时,请耐心等待。

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

例如,客户端会调用来获取一个对象:

Car car = getCar();

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

if (car != null){
    color = car.getColor();
    doScreenStuff(color);
   }

使用 NOP,而不是getCar()返回 null,它现在返回一个有效地“清零”的对象。所以现在我们不再需要做if (car != null),只需请求颜色即可。所以,我想当我们调用颜色时,我们的“归零”对象会返回“无”。

这有什么帮助?似乎向前移动并在空对象上调用方法会导致与检查 null 一样多的痛苦。现在,当需要显示信息时,我们需要检查颜色是否为“无”,高度是否为 0,或者您拥有的任何其他值。所以本质上,不是在处理开始时检查汽车是否为空,而是在之后检查我们拥有的汽车对象是真车还是替代品。IE我们不想显示一堆空对象,所以我们需要一些方法来过滤掉我们所有的空对象。

此过滤是一个附加步骤,就像调用 if (car != null) 一样。唯一的区别是,通过检查 null,我们可以通过抛出异常来在发现 car 对象为 null 时立即停止处理,而使用 NOP,我们会调用空对象上的方法并继续运行,直到时间到了显示对象,此时我们过滤掉空对象。此外,您需要知道空对象返回的值。IE 确实 getColor() 返回“无”或“空”。

显然必须有一些我忽略的东西。提前致谢。

4

6 回答 6

14

MattPutnam 的回答是正确的,我支持它。我要补充一点:“空对象”的概念,当您分析它时,似乎可以归结为monoid的数学概念。你可以这样想:一个幺半群是一种同时具有这两个东西的类型:

  1. 需要关联的“追加”、“求和”或类似操作:a.op(b).op(c)a.op(b.op(c)).
  2. 一个“空”、“零”或“空”值,充当操作的中性元素标识元素

空对象模式的经典示例是返回一个空列表或数组而不是 null。嗯,列表是一个幺半群,附加作为操作,空列表作为中性元素。

现在,您在Car示例中面临的问题是它Car并不是真正的幺半群;没有“空车”或“空车”的概念,也没有真正明智的操作可以用来将两个Cars 合并为一个。

因此,您正确获得的建议是使用 Java 8 之类的东西Optional。诀窍是,无论是什么类型TOptional<T>都是一个幺半群:

  1. monoid 的“组合”操作是“如果不是则选择第一个值empty,否则选择第二个值”:
    • x || empty = x
    • empty || x = x
  2. 中性元素是Optional.empty(),因为Optional.empty().orElse(anything)和刚才一样anything

所以基本上,Optional<T>是一个包装器,它向没有空对象的类型添加一个空对象Car。该Optional<T>.orElse(T value)方法是“选择第一个非empty值”幺半群的略微重构版本。

于 2015-06-08T23:16:32.000 回答
8

只有当空对象具有合理的功能值时,空对象模式才有意义。正如您所描述的那样,目的不是推迟 null ,而是通过用仍然有效的实际数据表示虚无或空虚来完全消除 null 的想法。例如,如 Wikipedia 文章中所述,树结构中的孔洞的自然情况。

空车没有意义。在这种情况下,似乎更合适的事情是getCar()return Optional<Car>

于 2015-06-08T20:10:34.247 回答
2

如果您不明白这一点,那么它可能不是一个适合您的范例。OO 编程的整个想法是让您的事情变得更简单。不要陷入认为您需要采用其他人精心设计的基于模式的系统的想法。学习各种模式并有效地使用它们通常需要大量的工作,所以最好是去适应它们,而不是试图强迫自己使用它。

就这种特定模式而言,它假定了某种可能不适合您的编程风格。我自己永远不会使用它,因为我将空值作为合法值(缺失数据)返回,这些值在每种情况下的处理方式都不同,因此“集中处理”对我来说毫无意义。当我返回布尔值时,我使用原语。

这里的底线是,如果一个模式对你来说不自然,就不要使用它。

于 2015-06-08T20:21:53.623 回答
0

至少根据我的经验,空指针对象是这样的,因此您将空检查限制在一个中心位置,以避免空指针异常。

如果很多服务都在使用 CarFactory,那么让 Carfactory 处理 null 结果会容易得多,然后让每个单独的服务处理它。此外,它确保每个空结果都以相同的方式处理,无论是什么都不做还是某些特定的逻辑。缺点是如果处理不当,可能会导致一些暂时令人困惑的错误(特别是因为空指针异常大声而自豪)。

我真的不再使用它了。除了使用空检查,还有其他方法,例如使用 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);
于 2015-06-08T20:14:59.490 回答
0

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

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

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

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 并返回零。如果您闭上眼睛并从逻辑上思考,这实际上是默认客户没有折扣的业务场景。

因此,与其通过检查 NULL 从技术上解决这个问题,我们可以创建一个如下所示的默认折扣行为类,该类返回零折扣并将其用于默认客户。这比检查 NULL 更干净。

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

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

由于 NULL 设计模式,避免了 NULL 检查,但这更多的是后效和副作用,而不是主要意图。

以上所有想法都是我从 C# 中的 NULL 设计模式中得出的,它解释了 NULL 的注意事项和注意事项。

于 2015-11-22T03:22:03.917 回答
-3

在 Car 上返回 Optional 不会取消检查 car 对象是否实际存在的步骤。

还车时,你会检查 if(car != null)。同样,在访问 Optional 时,您应该检查 if(car.isPresent()) 然后 car.get()。

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

您可能会问这个问题.. 如果我们检查它在两种模式中的存在,我们会因此获得什么优势?

答案在于知道您必须在使用它之前进行检查。如果您严格遵循在任何可以为空的地方返回 Optional 的做法,那么只要它不是可选的,您就不需要检查空值。

它作为一种记录方法的编程方式,在内部有助于通过检查样式执行检查。

于 2016-05-04T03:08:41.567 回答