1、 什么是Null对象模式?为什么要采用Null对象?
个人理解,所谓模式就是程序员们在编程的时候对一些类似问题总结出的通用解决方法或思路。因此,即使你的问题非常吻合某种设计模式所对应的问题,也不能指望现有的某种设计模式能够完全解决我们编程中的具体设计问题。
闲言少述,进入正题:
Null对象模式,顾名思义就是一种空的对象,它什么都不干。
或许你要问:既然它什么都不干,那要它干嘛?且看下面这个null对象的经典例子:
代码段1: Employee e = DB.getEmployee(); If(null == e && e.isTimeToPay(today)) e.pay(); |
例子中,需要判断对象是否为null然后采用不同的动作,即是否支付薪水;如果你的程序中有很多判断“null == e”这样的东西,根据Martin Fowler在《重构》一书中的说法便是你的代码中有了坏的味道,需要搞掉它!!!!
如果采用了Null对象之后,由于DB.getEmployee()返回的Null对象与Employee对象的具有相同的接口,当返回了Null对象之后也可以使用代码段2,只是这时空对象什么都没有操作,上述代码就可以简化为:
代码段2: Employee e = DB.getEmployee(); If(e.isTimeToPay(today)) e.pay(); |
在程序开发中诸如此类问题有很多,例如下面这段增加avl树节点的函数代码,其功能为在一个avl树种增加一个节点(节点类的接口为IAVLNode,实现类为AVLNode),函数中首先判断根节点是否为空,如果为空则将新增节点设为根节点,否则找到合适的位置将新增节点插入。
代码段3:
//为根节点 { avlRoot = addNode; return; } IAVLNode curNode = avlRoot; IAVLNode parentNode = avlRoot; while(null != curNode) { parentNode = curNode; if(curNode.getValue() < addNode.getValue()) curNode = curNode.getRightChild(); else curNode = curNode.getLeftChild(); } if(parentNode.getValue()< addNode.getValue()) parentNode.setRightChild(addNode); else parentNode.setLeftChild(addNode); addNode.setParentNode(parentNode); } |
在上面的一个函数代码中就有两处需要判断节点是否为空的操作,很显然,如果你要写一个AVL树旋转(就是在平衡二叉搜索树种增加节点后的四种调整平衡的操作,即左左插入时需右旋,右右插入时需左旋,左右插入时需左右两次旋转,右左插入时需要右左两次旋转)的代码,你的代码中判断节点是否为空的操作会有很多!!!!!
2、 如何定义和使用Null对象?
继续接着第1节的avl树的例子说明一下如何定义和使用Null对象,尤其注意,在实际操作中可采用的实现方式有很多种,本文只是说明其中一个,不要为此而禁锢了自己的思维。
Null对象只需要一个,因此这里采用了单例模式,具体定义如下:
代码段4:
public class NullNode implements IAVLNode { private static NullNode nullObj= null; private NullNode(){} public static NullNode GetNullObj() { if(null == nullObj) nullObj = new NullNode(); return nullObj; } @Override public boolean isNullObj() { // TODO Auto-generated method stub return true; } @Override public void DisplayInfo() { } @Override public IAVLNode getParentNode() { return null; } @Override public void setParentNode(IAVLNode parentNode) { } @Override public int getValue() { return 0; } @Override public void setValue(int value) { } @Override public IAVLNode getLeftChild() { return null; } @Override public void setLeftChild(IAVLNode leftChild) { } @Override public IAVLNode getRightChild() { return null; } @Override public void setRightChild(IAVLNode rightChild) { } @Override public int getDepth() { return 0; } @Override public void setDepth(int depth) { } @Override public boolean isBalance() { return true; } } |
在该Null对象中,继承自了接口IAVLNode,实现了该接口的所有操作,但这些操作什么都没有干。
3、 Null对象还有哪些不足?
1) 本文开始时说到Null对象主要解决问题之一就是程序中大量对null的判断;可是在实际应用中,并不是所有的诸如“null == avlRoot”之类的代码都能够全部去掉,部分情况下确实需要判断对象是否为空的时候依然需要调用“avlRoot.isNull”。
2) 有些时候空对象的空函数实现起来比较困难,例如在代码段3中的getValue()函数,这里就不太好确定让Null对象的该函数返回什么值,在代码段4中让它返回了0,如果我在程序中需要比较节点之间值的大小,这时空对象返回的0值就不恰当了。