空对象模式

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

我没有发现任何完全令人信服的东西。 当我试着整理自己的想法时,请在这里忍受我。

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

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

Car car = getCar(); 

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

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

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

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

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

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

MattPutnam的回答恰到好处,我接下来了。 我要补充一点:当你分析它时,“空对象”的概念似乎归结为幺半群的数学概念。 你可以这样想:monoid是一种具有这两种东西的类型:

  1. “附加”,“求和”或类似操作,需要关联a.op(b).op(c)a.op(b.op(c))
  2. “空”,“零”或“空”值,用作操作的中性元素标识元素

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

现在,你在Car例子中遇到的问题是Car并不是真正的幺半群; 没有“空车”或“中性车”的概念,并没有真正合理的操作可以用来将两辆Car合二为一。

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

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

所以基本上, Optional是一个包装器,它将一个空对象添加到没有一个类的Car这样的类型。 Optional.orElse(T value)方法,它是“选择第一个非empty值”monoid的略微重构版本。

只有null对象具有合理的function值时,空对象模式才有意义。 目的不是像你所描述的那样推迟null,而是通过用仍然有效的实际数据表示虚无或空虚来完全消除null的概念。 例如, 维基百科文章中描述的树结构中洞的自然情况。

零车没有意义。 在这种情况下,似乎更合适的是getCar()返回Optional

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

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

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

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

如果很多服务都在使用CarFactory,那么让Carfactory处理null结果要容易得多,然后让每个服务处理它。 此外,它确保每个null结果以相同的方式处理,无论是什么都不做或某些指定的逻辑。 缺点是,如果处理不当,可能会导致一些暂时混乱的错误(尤其是因为空指针exception会大声尖叫并自豪)。

我真的不再使用它了。 还有使用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); 

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

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

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

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

现在让我们说我们想要创建违约付款的客户。 所以我们inheritance了上面的类,因为很多属性和行为是相同的,但是我们不想要折扣计算,因为默认的客户没有资格享受折扣。 因此我们将折扣对象设为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更干净。

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

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

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

我从C#中的这个NULL设计模式中得到的所有上述想法都解释了Do和Don的Don。

返回可选汽车将不会取消检查汽车对象是否实际存在的步骤。

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

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

你可能会问这个问题。如果我们检查它是否存在于两种模式中,那么我们获得的优势是什么?

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

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