java1.8--Null Object模式

整理这篇博客是因为现在在整理java8中的optional,所以觉得很有必要整理下Null Object模式。java.lang.NullPointerException,只要敢自称Java程序员,那对这个异常就再熟悉不过了。为了防止抛出这个异常,我们经常会写出这样的代码:
Person person = people.find("LinkinPark...");
if (person != null) 
{
 person.doSomething();
}

遗憾的是,在绝大多数Java代码里,我们常常忘记了判断空引用,所以,NullPointerException便也随之而来了。“Null Sucks.”,这就是Doug Lea对空的评价。作为一个Java程序员,如果你还不知道Doug Lea是谁,那赶紧补课,没有他的贡献,我们还只能用着Java最原始的装备处理多线程。"I call it my billion-dollar mistake.",有资格说这话是空引用的发明者,Sir C. A. R. Hoare。你可以不知道Doug Lea,但你一定要知道这位老人家,否则,你便没资格使用快速排序。

OK,现在我们就开始整理下Null Object模式。

在整理Null Object模式之前,我们先来了解下多态的另外一个好处。多态的最根本好处在于,你不必再向对象询问“你是什么类型”而后根据得到的答案调用对象的某个行为,你只管调用该行为就是了,其他的一切多态机制会为你安排妥当。当某个字段内容是null时,多态可扮演另一个较不直观,也较不为人所知的用途。就是设定一种可能某种状态是null的对象,然后在原来对象有可能出现null状态判断的时候不用直接在这个对象上控制,而是用现在这个null object来取代原来的对象。具体的我们来举一个例子来研究下:

下面是原始代码:

package com.linkin.maven.mavenTest;


public class Test
{
	private String customerName;//模拟一个顾客名字


	//下面方法模拟业务逻辑处理代码
	public void test(Site site)
	{
		if (site.getCustomer() == null)
		{
			customerName = "这里是可能出现null值的情况";
		}
		else
		{
			customerName = site.getCustomer().getName();
		}
		System.out.println(customerName);
	}


	public String getCustomerName()
	{
		return customerName;
	}


	public static void main(String[] args)
	{
		Test test = new Test();
		Site site = new Site();
		//先模拟可能出现null值的情况
		test.test(site);
		//模拟正常的情况
		site.setCustomer(new Customer());
		test.test(site);
	}


}


/**
 * @创建作者: LinkinPark
 * @创建时间: 2015年10月30日
 * @功能描述: 模拟一个场所类,里面有一个顾客属性表示顾客。
 */
class Site
{
	private Customer customer;


	public Customer getCustomer()
	{
		return customer;
	}


	public void setCustomer(Customer customer)
	{
		this.customer = customer;
	}
}


/**
 * @创建作者: LinkinPark
 * @创建时间: 2015年10月30日
 * @功能描述: 模拟一个顾客类
 */
class Customer
{
	public String getName()
	{
		return "这里是正常情况的返回值";


	}


}

上面的代码我们看到了,里面有一个null值判断,如果是null处理null值情况,如果非null那么处理正常逻辑。现在我们用Null Object模式来将上面出现的null的情况的逻辑代码抽离到null object对象里面。现在我们在原来的基础上修改,代码如下:
package com.linkin.maven.mavenTest;


public class Test
{
	private String customerName;//模拟一个顾客名字


	//下面方法模拟业务逻辑处理代码
	public void test(Site site)
	{
		Customer customer = site.getCustomer();
		if (customer.isNull())
		{
			customerName = "这里是可能出现null值的情况";
		}
		else
		{
			customerName = customer.getName();
		}
		System.out.println(customerName);
	}


	public String getCustomerName()
	{
		return customerName;
	}


	public static void main(String[] args)
	{
		Test test = new Test();
		Site site = new Site();
		//先模拟可能出现null值的情况
		test.test(site);
		//模拟正常的情况
		site.setCustomer(new Customer());
		test.test(site);
	}


}


/**
 * @创建作者: LinkinPark
 * @创建时间: 2015年10月30日
 * @功能描述: 模拟一个场所类,里面有一个顾客属性表示顾客。
 */
class Site
{
	private Customer customer;


	public Customer getCustomer()
	{
		return customer == null ? Customer.newNull() : customer;
	}


	public void setCustomer(Customer customer)
	{
		this.customer = customer;
	}


}


/**
 * @创建作者: LinkinPark
 * @创建时间: 2015年10月30日
 * @功能描述: 模拟一个顾客类
 */
class Customer implements Nullable
{
	public String getName()
	{
		return "这里是正常情况的返回值";
	}


	//个人建议创建一个工厂函数,专门用来创建NullCustomer()对象,这样子外部就不知道这个空对象的存在了。
	static Customer newNull()
	{
		return new NullCustomer();
	}


	@Override
	public boolean isNull()
	{
		return false;
	}


}


class NullCustomer extends Customer implements Nullable
{


	@Override
	public boolean isNull()
	{
		return true;
	}


}


interface Nullable
{
	boolean isNull();
}

OK,现在我们重新定义了一个NullCustomer的对象用来做Null Object对象,但是我们的控制分支还在,现在我们继续重构,将上面的null值情况的业务处理代码搬入到null Object对象中,就可以去掉null分支判断了,我们在上面的基础上继续重构,代码如下:

package com.linkin.maven.mavenTest;


public class Test
{
	private String customerName;//模拟一个顾客名字


	//下面方法模拟业务逻辑处理代码
	public void test(Site site)
	{
		Customer customer = site.getCustomer();
		//将原来的控制分支利用java多态,分别移入到了2个对象中,这里就不存在分支了
		customerName = customer.getName();
		System.out.println(customerName);
	}


	public String getCustomerName()
	{
		return customerName;
	}


	public static void main(String[] args)
	{
		Test test = new Test();
		Site site = new Site();
		//先模拟可能出现null值的情况
		test.test(site);
		//模拟正常的情况
		site.setCustomer(new Customer());
		test.test(site);
	}


}


/**
 * @创建作者: LinkinPark
 * @创建时间: 2015年10月30日
 * @功能描述: 模拟一个场所类,里面有一个顾客属性表示顾客。
 */
class Site
{
	private Customer customer;


	public Customer getCustomer()
	{
		return customer == null ? Customer.newNull() : customer;
	}


	public void setCustomer(Customer customer)
	{
		this.customer = customer;
	}


}


/**
 * @创建作者: LinkinPark
 * @创建时间: 2015年10月30日
 * @功能描述: 模拟一个顾客类
 */
class Customer implements Nullable
{


	@Override
	public boolean isNull()
	{
		return false;
	}


	//正常分支不变,还是处理原来的逻辑
	public String getName()
	{
		return "这里是正常情况的返回值";
	}


	//个人建议创建一个工厂函数,专门用来创建NullCustomer()对象,这样子外部就不知道这个空对象的存在了。
	static Customer newNull()
	{
		return new NullCustomer();
	}


}


class NullCustomer extends Customer implements Nullable
{


	@Override
	public boolean isNull()
	{
		return true;
	}


	//原始业务代码逻辑中的控制分支中,出现Null值情况的分支移入到Null Object对象中了
	public String getName()
	{
		return "这里是可能出现null值的情况";
	}


}


interface Nullable
{
	boolean isNull();
}


OK了,现在代码写完了,完美的去掉了null值判断,关于null值可能出现的情况的业务处理代码我们都放到了Null Object里面了去。

关于Null Object记住一点,空对象一定是常量,它们的任何成分都不会发生变化。因此我们应该使用单例模式,这样子就不管什么时候,我们获得的Null Object对象都是同一个唯一实例了。
下面的代码是最终代码:
package com.linkin.maven.mavenTest;


public class Test
{
	private String customerName;//模拟一个顾客名字


	//下面方法模拟业务逻辑处理代码
	public void test(Site site)
	{
		Customer customer = site.getCustomer();
		//将原来的控制分支利用java多态,分别移入到了2个对象中,这里就不存在分支了
		customerName = customer.getName();
		System.out.println(customerName);
	}


	public String getCustomerName()
	{
		return customerName;
	}


	public static void main(String[] args)
	{
		Test test = new Test();
		Site site = new Site();
		//先模拟可能出现null值的情况
		test.test(site);
		//模拟正常的情况
		site.setCustomer(new Customer());
		test.test(site);
	}


}


/**
 * @创建作者: LinkinPark
 * @创建时间: 2015年10月30日
 * @功能描述: 模拟一个场所类,里面有一个顾客属性表示顾客。
 */
class Site
{
	private Customer customer;


	public Customer getCustomer()
	{
		return customer == null ? Customer.newNull() : customer;
	}


	public void setCustomer(Customer customer)
	{
		this.customer = customer;
	}


}


/**
 * @创建作者: LinkinPark
 * @创建时间: 2015年10月30日
 * @功能描述: 模拟一个顾客类
 */
class Customer implements Nullable
{


	@Override
	public boolean isNull()
	{
		return false;
	}


	//正常分支不变,还是处理原来的逻辑
	public String getName()
	{
		return "这里是正常情况的返回值";
	}


	//个人建议创建一个工厂函数,专门用来创建NullCustomer()对象,这样子外部就不知道这个空对象的存在了。
	static Customer newNull()
	{
		return NullCustomer.getInstance();
	}


}


/**
 * @创建作者: LinkinPark
 * @创建时间: 2015年10月30日
 * @功能描述: Null Object对象,使用单例模式,继承正常业务对象,实现null值判断接口
 */
class NullCustomer extends Customer implements Nullable
{
	private static NullCustomer nullCustomer;


	private NullCustomer()
	{


	}


	public static NullCustomer getInstance()
	{
		if (nullCustomer == null)
		{
			return new NullCustomer();
		}
		else
		{
			return nullCustomer;
		}
	}


	@Override
	public boolean isNull()
	{
		return true;
	}


	//原始业务代码逻辑中的控制分支中,出现Null值情况的分支移入到Null Object对象中了
	public String getName()
	{
		return "这里是可能出现null值的情况";
	}


}


/**
 * @创建作者: LinkinPark
 * @创建时间: 2015年10月30日
 * @功能描述: Null值判断接口,正常业务对象和Null值对象都应该实现的接口
 */
interface Nullable
{
	boolean isNull();
}

关于上面的重构,以后我会有专门的博客来整理重构的,这里说的就是要一遍重构,一遍测试。上面4个代码的测试结果都一样,也就是表示重构的没问题。下面我贴出上面的控制台输出:



总结:
在Java世界里,解决空引用问题常见的一种办法是,使用Null Object模式。这样的话,在“没有什么”的情况下,就返回Null Object,客户端代码就不用判断是否为空了。但是,这种做法也有一些问题。首先,我们肯定要为Null Object编写代码,而且,如果我们想大规模应用这个模式,我们要为几乎每个类编写Null Object。然后,有的时候业务处理稍微复杂一点,比如一个类包含了多个业务对象属性,这个时候的Null Object对象就会有好几个,编码量也大大的增加了。我们有必要了解这种合理的绕开null值判断的情况,但是如果实际编码这样子写Null Object类的话,也是一件很恶心的事情。java1.8中引入了Optional类,使用这个类我们可以很方便的实现上面的功能而且不用写很多代码的,这也就是我下篇博客要整理的。


原文地址:https://www.cnblogs.com/LinkinPark/p/5232969.html