【问题标题】:Null Object Pattern空对象模式
【发布时间】:2015-06-08 19:57:25
【问题描述】:

似乎有越来越多的人说您永远不应该返回 null,而应该始终使用 Null 对象模式。在使用集合/映射/数组或调用诸如 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,或者您拥有的任何其他值。所以本质上,不是在处理开始时检查汽车是否为空,而是在之后检查我们拥有的汽车对象是真车还是替代品。 IE。我们不想显示一堆空对象,所以我们需要一些方法来过滤掉我们所有的空对象。

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

显然我忽略了某些东西。 提前致谢。

【问题讨论】:

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


    【解决方案1】:

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

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

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

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

    因此,您正确获得的建议是使用 Java 8 Optional 之类的东西。而诀窍在于,无论T 是什么类型,Optional<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 值”幺半群的略微重构版本。

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

        【解决方案3】:

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

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

        这里的底线是,如果您觉得某个模式不自然,请不要使用它。

        【讨论】:

        【解决方案4】:

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

        如果很多服务都在使用 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);
        

        【讨论】:

        • 空对象模式和可选项的目的不同。 Optional 用于指示一个值可能存在也可能不存在。空对象模式只是对这样一个事实进行了编码,即可能存在仍然具有有用行为的“无”对象。
        • 对不起,我可能没有说清楚,我是说空检查的替代方案是可选项,然后空对象模式的“这绝不是空对象模式的替代品” .我已经有一段时间没有使用空对象模式了,因为我没有最近的用例,但我绝不建议用可选项替换空对象模式。
        【解决方案5】:

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

        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 检查,但仅此而已 后效和副作用,但不是主要意图。

        以上所有想法都是我从NULL Design pattern in C# 中获得的,它解释了 NULL 的注意事项。

        【讨论】:

          【解决方案6】:

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

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

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

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

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

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

          【讨论】:

          • 可选与空对象模式不同(en.wikipedia.org/wiki/Null_Object_pattern
          • Optional 仅用于isPresent()get() 就像在您全新的兰博基尼中仅以20 英里/小时的速度行驶。在这种情况下最好的做法是getCar().ifPresent(car -&gt; doScreenStuff(car.getColor()) )
          猜你喜欢
          • 2015-11-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-11-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-02-26
          相关资源
          最近更新 更多