使用Null Object设计模式时没注意到这一点就相当于白用
后台-插件-广告管理-内容页头部广告(手机) |
使用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
,使程序员聚焦于主逻辑,提升程序的可读性。
这样说可能太过抽象,我们还是先通过一个具体例子来体会一下Null Object设计模式的好处。
示例代码
假设我们要编写一个给小朋友使用的能播放各种动物叫声的程序。
我们先用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
改为null
或nil
,会怎么样呢?
接下来会以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
后台-插件-广告管理-内容页尾部广告(手机) |