前面我们已经详细介绍了空对象模式的概念、由来、实现以及利弊。并且我们也已经知道了使用空对象的目的主要在于解决下面两个问题:
1.去掉那该死的null检查
2.避免程序抛出NullPointerException异常
但是我们这里得提到一个说法:“被遗忘的空对象模式”。为什么说被遗忘呢,我个人的理解是:应用少而被遗忘。反正我在编程的过程中,对此模式的应用也是少之又少。因此,我认为知道它是怎么回事就可以了。
但是最近在看《Thinking in Java 4th》的时候发现,原来空对象还可以这么用。这不,忍不住,想和大家一起分享探讨一下。
问题描述
当黑子的公司规模发展到一定程度时,他的任务不再是那么轻松了,整天没事尽搞些瞎几把查询。它得为应对公司发展的需要设置许多新的职位,并且为这些职位招聘天下英才。
实现
1.创建标记接口
我们知道,正常情况下,我们使用一个对象的引用时,第一步是要对对象进行是否为空的判断,才能防止异常的发生。而我们知道,学过“空对象模式”后,我们就可以使用空对象来解决问题。
但是,在初阶中我们也提到了,即使你使用了“空对象模式”,最好的策略是仍然进行null的判断,不过此时,我们的方法是,创建一抽象接口,在接口内定义isNull()方法,进行判断。但最简单的方式是创建一个标记接口。
package com.yc.null_object;
/**
* Created by yucheng on 2018/8/9.
* 说明:创建一个标记接口,对对象进行是否为空的最简单判断方式
* 使得我们可以使用instanceof探测空对象,毕竟isNull()和
* instanceof只是RTTI(运行时类型识别)的不同方式,为什么
* 不用内置的呢
*/
public interface Null {
}
2.创建“含空对象实体类”的非空的实体类
说明:表示空对象类,此处是放在Person的内部作为一个静态内部类的存在(当然你将其独立出来也没毛病),个人觉得,结构虽然变得复杂了一些,但是整体性更好。不得不佩服大神的实力!
而且空对象,通常是单例的(因为我们只需要一个呀),因此我们可以将其作为static final(编译期常量)来创建,可以减少运行时的压力嘛!
package com.yc.null_object;
/**
* Created by yucheng on 2018/8/9.
*/
public class Person {
public final String first;
public final String last;
public final String adress;
public Person(String first, String last, String adress) {
this.first = first;
this.last = last;
this.adress = adress;
}
@Override
public String toString() {
return "Person{" +
"first='" + first + '\'' +
", last='" + last + '\'' +
", adress='" + adress + '\'' +
'}';
}
// 创建一个静态内部类,用来创建空对象
public static class NullPerson extends Person implements Null{
public NullPerson() {
super("None", "None", "None");
}
@Override
public String toString() {
return "NullPerson";
}
}
// 创建一个编译期常量,并将其初始化为空对象
public static final Person NULL = new NullPerson();
}
3.创建职位实体(Position)
现在到了黑子招兵买马,大展身手的时候了。但是在虚位以待时,我们就可以将Person空对象放在每一个Position上。即我们为每个职位预留一个名额,没有人时,我们在上面放空对象,当招聘到了人以后,我们就将新员工填充到到该职位上。
package com.yc.null_object;
/**
* Created by yucheng on 2018/8/9.
* Position表示公司的职位,但此时也许有的职位还没有人,因此我们可以先将
* 这个职位留出来,只不过,用空对象对它进行填充,当以后需要的时候,再对其进行填充
*/
public class Position {
private String title;
private Person person;
// 创建有人任职的职位
public Position(String jobTitle, Person Employee) {
this.title = jobTitle;
this.person = Employee;
if (person == null){
// 如果对象为空,就将其转为NullPerson空对象
person = Person.NULL;
}
}
// 创建没有人任职的职位
public Position(String jobTitle) {
this.title = jobTitle;
// 当构造器中没有传入Person对象,默认将其设置为NullPerson空对象
person = Person.NULL;
}
public String getTitle() {
return title;
}
public void setTitle(String newTitle) {
this.title = newTitle;
}
public Person getPerson() {
return person;
}
public void setPerson(Person newPerson) {
this.person = newPerson;
if (person == null){
person = Person.NULL;
}
}
@Override
public String toString() {
return "Position{" +
"title='" + title + '\'' +
", person=" + person +
'}';
}
}
4.创建Staff类,进行Position对象的创建、存储和查询
这是一个非常有创造力的类,简洁且功能强大!
废话先不多说,直接上程序!
package com.yc.null_object;
import java.util.ArrayList;
/**
* Created by yucheng on 2018/8/10.
*/
public class Staff extends ArrayList<Position>{
// 我们可以将Staff重载的add方法,显示的写出来,如下:
// 发现,它是调用了父类的add方法,将Position对象存储在对象中,
// 因为,Staff继承自ArrayList,因此它本身也是一个List
@Override
public boolean add(Position position) {
return super.add(position);
}
// 自行对add方法进行重载
public void add(String title,Person person){
// 此处add()方法是继承自ArrayList
add(new Position(title,person));
}
// 自行对add方法进行重载
public void add(String... titles) {
for (String title : titles) {
// 创建空职位,此处调用的是默认的从父类继承来的add方法
add(new Position(title));
}
}
// 构造器
public Staff(String... titles){
add(titles);
}
// 判断职位是否可以填充
public boolean positionAvailable(String title){
// 此处的this表示的是调用此方法的Staff对象
for (Position position:this) {
if (position.getTitle().equals(title)&&position.getPerson() == Person.NULL){
return true;
}
}
return false;
}
// 填充职位
public void fillPosition(String title,Person hire){
for (Position position:this){
if (position.getTitle().equals(title)&&position.getPerson() == Person.NULL){
position.setPerson(hire);
return;
}
}
throw new RuntimeException("Position " + title + "not available");
}
// 测试
public static void main(String[] args) {
Staff staff = new Staff("President","CTO",
"Marketing Manager","Product Manager",
"Project Lead","Software Engineer",
"Software Engineer","Software Engineer",
"Software Engineer","Test Engineer","Technical Writer");
staff.fillPosition("Project Lead",new Person("Janet","Planner","The Top ,Lonely At"));
staff.fillPosition("President",new Person("Me","Last","The Burbs"));
if (staff.positionAvailable("Software Engineer")){
staff.fillPosition("Software Engineer",new Person("Bob","Coder","Bright Light City"));
}
System.out.println(staff);
}
}
为什么说这个class厉害呢,下面我们来分析一下:
1.继承自ArrayList,使得对象具有List的存储功能
public class Staff extends ArrayList<Position>
这样一来,我们的Staff对象就能够想List一样对Position对象进行存储、查询等操作,如add()。
2.不定参数的方式,使得我们一次可以创建多个Position对象
public void add(String... titles) {}
这样,我们就可以一次传入多个职位名称titles,然后循环调用构造器,就行对象的创建,而不必一个个的创建了。
3.方法的重载
// 第一个add方法
@Override
public boolean add(Position position) {
return super.add(position);
}
// 第二个add方法
public void add(String title,Person person){
// 此处add()方法是继承自ArrayList
add(new Position(title,person));
}
// 第三个add方法
public void add(String... titles) {
for (String title : titles) {
// 创建空职位
add(new Position(title));
}
}
说明:
第一个add方法:是继承自父类ArrayList,是对Position对象进行操作
第二个add方法:是我们自己定义的add重载方法,目的在于将非空Position对象存储到Staff中。
第二个add方法:是我们自己定义的add重载方法,目的在于将空Position对象存储到Staff中。Staff此时就是一个ArrayList。
总结
山重水复疑无路,柳暗花明又一村!只有你想不到,没有做不到!长知识了!
提示:下一节,我将继续“空对象模式”的最后一节,用动态代理创建空对象
推荐阅读:
设计模式--空对象模式(Null Object Pattern)-初阶
设计模式--代理模式(Proxy Pattern)
设计模式--代理模式(Proxy Pattern) 之 “高老庄悟空降八戒”