您现在的位置是:首页 > 技术教程 正文

使用Null Object设计模式时没注意到这一点就相当于白用

admin 阅读: 2024-04-30
后台-插件-广告管理-内容页头部广告(手机)

使用Null Object设计模式时没注意到这一点就相当于白用

在各种项目中,我们可能会反复看到类似下面这样的代码

returnReference = call_a_function()
if returnReference == null {
  // 如果函数返回的引用/指针为null,表示需要获取的对象/值不存在
  // 执行处理异常情况的逻辑
} else {
  // 需要的对象/值存在
  // 执行处理正常情况的主逻辑
  returnReference.doSomething()
}

也就是说,我们往往必须检查函数或方法的返回值,先确保其不为Null再调用上面的方法(向其发送消息)。这是因为对Null调用方法通常会报错(是的,存在不会报错的特殊情况)。当然,对于函数的参数,局部或全局变量等也需要相同的检查。

注:本文用首字母大写的Null泛指各种语言中的空指针或空引用;用代码体的null(PHP、Java)或nil(Go)特指对应语言中的空指针或空引用。

对Null调用方法有可能不报错吗?试试文末那段Go代码吧^_^

为了防止这种判断是否为Null的if else在项目中遍地开花,前辈程序员们发明了Null Object这一设计模式。该模式是用称为null object(空对象)的特殊对象来取代Null,改用这种仅带有空值和空方法的对象来表示不存在、未知、无意义等异常数据

如图所示,用null object取代Null就能消除大量if else,使程序员聚焦于主逻辑,提升程序的可读性。

image-Dozens of checks for null make your code longer and uglier

这样说可能太过抽象,我们还是先通过一个具体例子来体会一下Null Object设计模式的好处。

示例代码

假设我们要编写一个给小朋友使用的能播放各种动物叫声的程序。

Animal Sounds

我们先用PHP来编写这段程序。

interface Animal {
    public function makeSound();
}

class Dog implements Animal {
    public function makeSound() {
        echo "WOOF\n";
    }
}

class Cat implements Animal {
// ...
  
function makeAnimalFromAnimalType(string $animalType): Animal {
    switch ($animalType) {
        case 'dog':
            return new Dog();
        case 'cat':
            return new Cat();
        // ...
    }
}
  
makeAnimalFromAnimalType('dog')->makeSound();

我们这里用输出语句(echo语句)代替播放动物的叫声。如果小朋友想听小狗叫,这个程序就会通过makeAnimalFromAnimalType('dog')创建出Dog类的对象,然后调用上面的makeSound()方法,这样就会”听“到“汪汪汪“(WOOF)的叫声。小猫喵喵叫也是同样的逻辑。

但如果小朋友想知道小兔子怎么叫呢?

我们没有定义Rabbit类,而且也不知道兔子怎么叫。就算问了专家,知道了兔子的叫声,那狐狸怎么叫呢?小朋友总能想出叫声未知的动物。

这时Null Object模式就派上用场了。

首先我们定义出一个NullAnimal类①,并实现了makeSound()方法,只不过方法里什么也没有做。

class NullAnimal implements Animal { // ①
    public function makeSound() {
        // silence...
    }
}

function makeAnimalFromAnimalType(string $animalType): Animal {
    switch ($animalType) {
        case 'dog':
            return new Dog();
        case 'cat':
            return new Cat();
        default:
            return new NullAnimal();    // ②
    }
}

$animalType = 'rabbit';
makeAnimalFromAnimalType($animalType)->makeSound(); // ③ ..the null animal makes no sound

然后我们在函数makeAnimalFromAnimalType()中为所有叫声未知的动物统统返回new NullAnimal()②。

只需这两步,我们就可以放心对makeAnimalFromAnimalType()的返回值调用makeSound()了。因为该函数不会返回null③,也就防止后续代码对null调用makeSound()了。这就意味着无须再通过if makeAnimalFromAnimalType($animalType) == null进行判断了。

Java、Go等语言的代码与此大同小异,这里就不一一列举了。

怎么样,这个设计模式很简单吧。

需要特别注意的点在哪里

Null Object模式看似简单,但正如本文标题说的,里面有一个特别需要注意的点,一旦没处理好就等于前功尽弃

在揭晓答案之前,我们再来重点看一看这个根据动物的名称返回动物类实例的makeAnimalFromAnimalType()的函数。

那么问题来了,如果把default分支返回的null object NullAnimal改为nullnil,会怎么样呢?

接下来会以PHP、Java和Go这三种主流语言为例。首先从PHP的代码看起。

<?php
function makeAnimalFromAnimalType(string $animalType): Animal {
    switch ($animalType) {
        case 'dog':
            return new Dog();
        case 'cat':
            return new Cat();
        default:
            return null;    // <--
    }
}

makeAnimalFromAnimalType("rabbit");
// Fatal error: Uncaught TypeError: makeAnimalFromAnimalType(): Return value must be of type Animal, null returned in ...

报错了!(有点意外吧)因为makeAnimalFromAnimalType()之后的类型提示: Animal强制要求该函数返回一个类型为Animal的值,而null不是。

再来看看Java中的情况。

interface Animal {
    void makeSound();
}

class Dog implements Animal {
// ..

class NullAnimal implements Animal {
// ..

public class AnimalSound {
    public static Animal makeAnimalFromAnimalType(String animalType) {
        switch (animalType) {
            case "dog":
                return new Dog();
            default:
                return null; // <--
        }
    }

    public static void main(String[] args) {
        Animal rabbit = makeAnimalFromAnimalType("rabbit");    // ①
        rabbit.makeSound(); // ②
    }
}
  
// Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Animal.makeSound()" because "" is null at AnimalSound.main(...

可以看到,不同于PHP,在Java中,makeAnimalFromAnimalType()可以返回null①,但是在null上调用makeSound()会抛出空指针异常②。

关于一个声明返回类型为T的方法能否返回null这个问题,在2015年于上海举办的PHP大会上,一位程序媛小姐姐还问过PHP大神鸟哥,鸟哥当时感叹道:人家这是认真听讲了。这一幕的视频在

标签:
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

在线投稿:投稿 站长QQ:1888636

后台-插件-广告管理-内容页尾部广告(手机)
关注我们

扫一扫关注我们,了解最新精彩内容

搜索