Null Object设计模式

 我们知道,在编写Java或Groovy代码时,当我们得到一个对象的引用并调用其方法时,如果这个引用是null,JVM将会报一个NullPointerException异常。我们处理此类问题的通常办法是通过try...catch(...)语句来捕获异常,并做一些异常补偿操作。但是有些时候,这种方式并不通解决所有问题,其问题在于:

       首先,我们通常通过方法参数得到对象的引用,由于我们不可能预先知道方法的调用者是否已经对方法参数进行过校验,因此,为了避免不必要的异常我们通常不得不在编写方法调用逻辑前先对传递进来的参数进行验证(记得一本介绍调试模式的书专门提到这一点,并鼓励程序员这样做)。可想而知,如果每个方法都要进行这种参数验证,工作量与代码量就非常巨大了,并且,这也不符合DRY(Don't Repeat Yourself)原则。

       其次,程序员也不可能预先知道API编写人员是否在方法内验证参数,作为一个严谨的程序员,也会在调用API前预先验证要传入的参数。同上面的问题一样,这样也会引起代码的臃肿。

       最后,就算是我们能够保证对象引用不为空,但是我们也不能保证对象属性也不为空,那么当我们调用对象属性的方法时,也有可能引起空指针异常。因此,对于这种问题,我们很难做到对每个对象的每个层次都验证到,那么有没有办法解决此类问题呢,Null Object模式就是解决如何处理此类问题最佳实践。

       我们先以一个例子开始:

 

class Job {
    def salary
}

class Person {
    def name
    def Job job
}

def people = [
    new Person(name:'Tom', job:new Job(salary:1000)),
    new Person(name:'Dick', job:new Job(salary:1200)),
]

def biggestSalary = people.collect{ p -> p.job.salary }.max()
println biggestSalary

      当我们运行时,将打印出12000,那么如果们如下调用:

people << new Person(name:'Harry')

      现在再调用biggestSalary方法,我们将得到空指针异常。为了解决此问题,我们定义一个NullJob类,

class NullJob extends Job { def salary = 0 }

people << new Person(name:'Harry', job:new NullJob())
biggestSalary = people.collect{ p -> p.job.salary }.max()
println biggestSalary
 
   或者如果我们可以改写Person类,也可以这样写:
class Person {
    def name
    def Job job = new NullJob()
}
class NullJob extends Job { def salary = 0 }

people << new Person(name:'Harry' )
biggestSalary = people.collect{ p -> p.job.salary }.max()
println biggestSalary

       其实,我们就是将Job为空这种情况用一个类来表示,同时,也在这个类中将对象为空时,其每个字段的默认值也定义清楚,这样就不会影响此对象的消费者调用逻辑。

       另外,我们还有一种更Groovy的方式来处理此类问题,那就是使用?.操作符,使用此操作符,就算是引用是空指针也不会报异常,而是将其做为null对象处理。例如:

people << new Person(name:'Harry')
biggestSalary = people.collect{ p -> p.job?.salary }.max()
println biggestSalary

       但这里一定要注意,max()是一个"null aware"的方法,也就是说,这个方法知道如何处理null对象,如果我们使用的方法不是"null ware”,那么这个操作符就不灵了。列如,下面的例子:

class NullHandlingTree {
    def left, right, value
    
    def size() {
        1 + (left ? left.size() : 0) + (right ? right.size() : 0)
    }

    def sum() {
       value + (left ? left.sum() : 0) + (right ? right.sum() : 0)
    }

    def product() {
       value * (left ? left.product() : 1) * (right ? right.product() : 1)
    }
}

def root = new NullHandlingTree(
    value:2,
    left: new NullHandlingTree(
        value:3,
        right: new NullHandlingTree(value:4),
        left: new NullHandlingTree(value:5)
    )
)

println root.size()
println root.sum()
println root.product()

      以上的例子中,我们不得不在size()、sum()、product()方法中加入对于空指针的判断逻辑,而这些校验逻辑和我们的业务逻辑是没有什么关系的,在这种情况下,我们就可以使用Null Object模式来解决。例如:

 

class Tree {
    def left = new NullTree(), right = new NullTree(), value
    
    def size() {
        1 + left.size() + right.size()
    }

    def sum() {
       value + left.sum() + right.sum()
    }

    def product() {
       value * left.product() * right.product()
    }
}
class NullTree {
    def size() { 0 }
    def sum() { 0 }
    def product() { 1 }
}

def root = new Tree(
    value:2,
    left: new Tree(
        value:3,
        right: new Tree(value:4),
        left: new Tree(value:5)
    )
)

println root.size()
println root.sum()
println root.product()
           通过定义一个NullTree类,来将空指针的情况进行定义,这样,Tree类就可以专注于真正的业务逻辑处理,同时,也省掉了不必要的验证代码。 
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值