背景:要从数据库中得到一个名叫Bob的员工(Employee),如果今天是其薪酬支付日期(isTimeToPay()),即为之支付薪酬(pay())。

对于长期进行C-Based语言开发的人,简单来说,代码大致如下:

Employee e = DB.getEmployee(“Bob”);
if(e != null && e.isTimeToPay()){
        e.pay();
}

这样的代码对我们长期写C-Based语言代码的人已经司空见惯,&&首在第一个表达式值为true的时候,才会执行第二个表达式。当然在也许会有人这样写:

Employee e = DB.getEmployee(“Bob”);
try{
       if(e.isTimeToPay())
     {
            e.pay();
     }
}catch(NullPointerException e){
      e.printStackTrace();
}

这两种写法本质上都没有什么错,但是都是及其繁琐的,对于经验不足的程序员,在大多时候都会忘记对e做是否为空的检查,导致异常抛出。

引入Null Object模式,即可很好的解决这个问题,即不需要对e是否为null值做检验,直接使用,不仅代码美观易懂,也排除了很多可能潜在的错误。

下面演示Null Object的魅力:

首先,建立Employee接口,如下:

import java.util.Date;
public interface Employee {
       public boolean isTimeToPay(Date payDate);
       public void pay();
      public static Employee NULL = new Employee() {
      @Override
       public void pay() {
       }
     @Override
     public boolean isTimeToPay(Date payDate) {
              return false;
     }
   };
}

该接口有一个名为NULL的静态变量持有一个匿名的Employee实体,这个匿名的实现体是无效雇员(null employee)类的唯一实例。其中isTimeToPay()返回false,而pay(0方法实现为空。

对于DB类,我们直接返回一个Employee.NULL ,以便测试:

public class DB {
      //为了测试,这里我们直接返回Employee.NULL
      public static Employee getEmployee(String name){
               //return null;
               return Employee.NULL;
      }
}

在JUnit下写出我们的测试方法:

public class TestEmployee {
     @Test
      public void testNullObject() throws Exception
      {
                Employee e = DB.getEmployee(“Bob”);
                if(e.isTimeToPay(new Date())){
                      e.pay();
         }
        assertEquals(Employee.NULL, e);
}
      @Test
       public void testCBased() throws Exception
      {
                Employee e = DB.getEmployee(“Bob”);
                if(e != null && e.isTimeToPay(new Date())){
                      e.pay();
                }
                assertEquals(null, e);
       }
      @Test
       public void testNoCheck() throws Exception
      {
                 Employee e = DB.getEmployee(“Bob”);
                 if(e.isTimeToPay(new Date())){
                         e.pay();
                 }
                 assertEquals(null, e);
        }
}

可见,使用Null Object不仅减少了代码上重复的检查对象是否为空,甚至直接排斥了那些没有良好参数检查习惯的编程人员可能会犯的错。使无效雇员类成为一个匿名类是一种确保该类只有一个实例的方法,实际上并不存在NullEmployee本身。任何其他的人都无法创建无效雇员类的其他实例。这样的话,才需要的场合我们可以使用如下表达式:

if(e == Emplyee.NULL)

如果可以创建无效雇员类的多个实例,那么以上表达式就是非常不可靠的。

长期使用C-Based语言的人习惯了返回null或者0或者-1,这样的函数返回值在使用前是必须经过检查的。NULL OBJECT模式改变了这一点。使用该模式,我们可以确保函数返回的总是有效对象,其实在他们失败时也是如此,因为这些代表失败的对象“什么也不做”。

参考:《Agile Software Development -Principles, Patterns, Practices》