空对象模式

时间:2015-06-08 19:57:26

标签: java oop nullpointerexception null null-object-pattern

似乎有越来越多的人说你永远不应该返回null,而应该总是使用Null对象模式。我可以在使用集合/ map /数组或调用boA函数(如isAuthenticated(),which is shown here时看到NOP的有用性。

我没有发现任何完全令人信服的内容。当我试图组织我的想法时,请在这里忍受。

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

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

Car car = getCar();

如果没有使用NOP,你需要检查从getCar()返回的对象是否为null,然后再调用它上面的任何方法:

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

使用NOP而不是getCar()返回null,它现在返回一个已被有效“清零”的Object。所以现在我们不再需要if (car != null)并且可以只请求颜色。所以,我认为当我们调用颜色时,我们的“归零”对象将返回“无”。

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

这个过滤是一个额外的步骤,就像调用if(car!= null)一样。唯一的区别是,通过检查null,我们可以在通过抛出异常发现car对象为null时立即停止处理,而使用NOP我们在空对象上调用方法并继续前进,直到它到达时间为止显示对象,此时我们过滤掉空心。此外,您需要知道空对象返回的值。 I.E. getColor()返回“none”或“empty”。

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

6 个答案:

答案 0 :(得分:12)

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

  1. “追加”,“求和”或类似操作,需要关联a.op(b).op(c)a.op(b.op(c))相同。
  2. “空”,“零”或“空”值,用作操作的中性元素标识元素
  3. 空对象模式的经典示例是返回空列表或数组而不是null。好吧,列表是一个幺半群,附加作为操作,空列表作为中性元素。

    现在,您在Car示例中遇到的问题是Car并非真正的幺半群;没有“空车”或“中性车”的概念,并且没有一个合理的操作可以用来将两个Car组合成一个。

    因此,您正确得到的建议是使用类似Java 8 Optional的内容。诀窍在于,无论T是什么类型,Optional<T>都是幺半群:

    1. monoid的“合并”操作是“如果它不是empty则选择第一个值,否则选择第二个值”:
      • x || empty = x
      • empty || x = x
    2. 中性元素为Optional.empty(),因为Optional.empty().orElse(anything)anything相同。
    3. 基本上,Optional<T>是一个包装器,它将空对象添加到没有一个类似Car的类型。 Optional<T>.orElse(T value)方法是“选择第一个非empty值”monoid的略微重构版本。

答案 1 :(得分:8)

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

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

答案 2 :(得分:2)

如果你没有看到这一点,那么它可能不是一个适合你的好范例。面向对象编程的整个想法是让事情变得更简单。不要陷入困境,认为你需要采用别人精心设计的基于模式的系统。通常需要花费大量的工作来学习各种模式并有效地使用它们,所以最好成长为它们,而不是试图强迫自己使用它。

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

这里的底线是,如果图案看起来不自然,请不要使用它。

答案 3 :(得分:0)

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

如果很多服务都在使用CarFactory,那么让Carfactory处理null结果要容易得多,然后让每个服务处理它。此外,它确保每个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);

答案 4 :(得分:-1)

NULL设计模式使用DEFAULT行为填充对象的ABSENCE,并且只应在一个对象与其他对象协作时使用。

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

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

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

现在让我们说我们想要创建违约付款的客户。所以我们继承了上面的类,因为很多属性和行为是相同的,但是我们不想要折扣计算,因为默认的客户没有资格享受折扣。因此我们将折扣对象设为null,但这是一个问题,因为现在折扣计算会因为NULL折扣对象而崩溃。

class DefaultedCustomer : Customer {
    IDiscount dis = null;
    public double CalculateDiscount() {
        return dis.Calculate(); <---- This will crash in parent
    }
    // Code removed for simplicity
}

因此,开发人员解决此问题的方法之一是检查NULL并返回零。如果你睁大眼睛并从逻辑上思考这实际上是商业场景,对于违约客户没有折扣。

因此,通过检查NULL,我们可以创建一个DEFAULT DISCOUNT BEHAVIOR类,而不是通过检查NULL来修复此问题,如下所示,它返回零折扣并对默认客户使用相同的折扣。这比检查NULL更干净。

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

需要NULL,但异常处理不是为了在协作中修复对象的缺失,如上所示。如果没有协作,则NULL设计模式没有意义。

  

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

我从这个NULL Design pattern in C#中解释了以上所有的想法,它解释了Do和Donts of Don。

答案 5 :(得分:-3)

返回可选汽车不会带走检查汽车对象是否确实存在的步骤。

当回车时,你会检查是否(car!= null)。 同样,当访问Optional时,你应该检查是否(car.isPresent())然后检查car.get()。

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

你可能会问这个问题。如果我们检查它是否存在于两种模式中,我们会得到什么好处?

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

它作为一种记录方法的程序化方式,在内部帮助通过检查方式执行检查。