1、使用场景
空设计模式在什么时候会派上用场呢?假设这样一个场景:在一个在线考试系统中,调用一个方法,传过去你要查找用户的ID,然后它返回给你要查找用户的考试成绩,此时你就可以调用对象的方法来输出用户的成绩信息。下面,我们来实现以下具体的代码。
public class User {
private int ID;
private String name;
private String score;
public User(int ID, String name, String score) {
this.ID = ID;
this.name = name;
this.score = score;
}
public void show() {
System.out.println(ID + "+++" + name + "+++" + score);
}
}
创建用户对象的用户工厂的代码:
public class UserFactory {
public User getUser(int ID) {
User user = null;
switch (ID) {
case 1:
user = new User(ID, "jack", "66");
break;
case 2:
user = new User(ID, "phil", "88");
break;
default:
user = null;// 可以省略。
break;
}
return user;
}
}
客户端调用代码:
public class Client {
static void main(String[] args) {
UserFactory userFactory = new UserFactory();
User user = userFactory.getUser(1);
user.show();
}
}
运行成功,但是如果我们把User user = userFactory.getUser(1)中的1改成-1。再来运行一下,发现如下报错:空指针报错。它提示我们第28行user.show();报错。因为我们通过userFactory.getUser(1)方法获取User对象的时候,如果我们传入的参数,属于非法值(如-1)或者不存在(如a)的话(其实这种情况是经常遇到的),就会返回null,表示我们查找的用户并不存在。这时,user为null.你再调用user.show()。当然要报空指针的错误了。那怎么解决呢?
我们比较常规的做法就是在客户端加一个判断,判断是否为null。如果为null的话,就不再调用show()方法。如果不为null再调用show()方法。更改如下:
public static void main(String[] args) {
UserFactory userFactory = new UserFactory();
User user = userFactory.getUser(-1);
//判断user对象是否为null。
if (user == null) {
System.out.println("user对象为 null。");
} else {
user.show();
}
}
此时,再运行,就不会报错了。而是,输出了:user对象为null。
但是,你有没有考虑过?这样做,确实消除了报错,但是这样做真的好吗?你想如果在一段程序中有很多处调用getUser()方法或者有很多个客户端的话,岂不是很多处都要判断user对象是否为null?这还不算坏,如果哪一处没有判断,然后报错了,很有可能导致程序没法继续运行甚至崩溃。那究竟应该如何实现才会更加合适呢?此时就要用到我今天要说的Null Object Pattern——空设计模式
2、空设计模式
下面我们来改一下代码,新增的抽象接口Users类的代码:
interface Users {
// 判断User对象是否为空对象(Null Object)
public boolean isNull();
// 展示user对象的信息
public void show();
}
新增的空对象类NullUser类的代码(继承Users类):
public class NullUser implements Users {
public boolean isNull() {
return true;
}
public void show() {
}
}
原有的User类修改后的代码(增加对Users接口的实现,实现isNull方法):
public class User implements Users{
private int ID;
private String name;
private String score;
// 构造函数
public User(int ID, String name, String score) {
this.ID = ID;
this.name = name;
this.score = score;
}
public void show() {
System.out.println(ID + "+++" + name + "+++" + score);
}
public boolean isNull(){
return false;
}
}
工厂类(UserFactory)修改后的代码(返回对象从User改为Users,并当ID属于非法值或者不存在时,返回NullUser对象。):
public class UserFactory {
public Users getUser(int ID) {
Users users;//将原来的User改为Users
switch (ID) {
case 1:
user = new User(ID, "jack", "66");
break;
case 2:
user = new User(ID, "phil", "88");
break;
default:
users = new NullUser();//创建一个NullUser对象
break;
}
return users;
}
}
客户端的代码为:
public static void main(String[] args) {
UserFactory userFactory = new UserFactory();
Users users = userFactory.getUser(-1);
users.show();
}
运行我们发现,即使传入的参数是非法值或者不存在的值时,也不会报错了,这是空设计模式(Null Object Pattern)的第一个好处。但是现在不报错,也没有任何输出,肯定不够友好,不够人性化。此时,在NullUser类的show方法中,我们可以定制我们的输出提醒,当用户调用空对象的show方法时,就会输出我们定制的提醒。这回我们可以实现,一处定制,处处输出,主动权在我们手里,而不是在客户端的手里。这是Null Object Pattern的第二个好处。
比如我们进行如下修改,修改后的NullUser类代码:
public class NullUser implements Users {
public boolean isNull() {
return true;
}
public void show() {
System.out.println("Sorry,未找到符合您输入的ID的用户信息,请确认您的输入是否正确。");
}
}
此时,在执行一下Client,你会发现控制台输出为:Sorry,未找到符合您输入的ID的用户信息,请确认您的输入是否正确。其实,虽然在客户端我们不进行检测也可以保证程序不报错,但是最好的方式,还是进行相应的检测,如下:
public static void main(String[] args) {
UserFactory userFactory = new UserFactory();
Users users = userFactory.getUser(-1);
if (users.isNull()) {
//这里由客户端定制提示代码
System.out.println("您的输入不正确。");
}else{
users.show();
}
}
相比之下,users.isNull()比users == null更加优雅一点。到这里,Null Object Pattern大概就介绍完了。我们可以看到,其实Null Object Pattern很简单,但是它也可以说使整个系统更加坚固了。
3、总结
(1)空设计模式可以加强系统的稳固性,能有有效地防止空指针报错对整个系统的影响,使系统更加稳定。
(2)它能够实现对空对象情况的定制化的控制,能够掌握处理空对象的主动权。
(3)它并不依靠Client来保证整个系统的稳定运行。
(4)它通过isNull对==null的替换,显得更加优雅,更加易懂。