设计模式一个要点就是可以封装变化点,使得这个变化点上的的扩张变可以变得很轻松。Null Object Pattern却不是。它没有封装任何变化点,甚至它的结构图仅仅就是一个简单的类继承结构:
所以以其说它是一个设计模式倒不如说是一个编程技巧。
下面我们来看几个实例:
1.我们来看一个简单的屏幕保护程序,它的功能就是在屏幕上显示一个可以移动且颜色可以变化的圆,假设移动和颜色变化是变化的方向。我们首先想到的就是用策略模式分别表示它颜色和移动。如下图:
根据依赖倒置原则我们可能先会设计Ball的逻辑,然后再设计IMotionStrategy和IColorStrategy。若我们只实现了继承于IMotionStrategy的类1,这个时候却想看看效果,一个常用的方式就是用最简单的方法实现一个IColorStrategy的子类,这个子类什么也没做。这样我们会看到一个拥有默认颜色的球体按我们类1预定的轨迹移动。其实这个什么也没做的类就是我们的Null Object。由此引出它的第一个好处。利于框架的搭设和单方面功能测试。以后实现类3来替换也是很简单的,因为Null Object完全基于IColorStrategy接口。当然这里IMotionStrategy和IColorStrategy是正交的。
2.有的时候我们程序需要写这样的代码来输出调试信息:
if (cond)
{
...
System.out.println("Cond is true.");
}
else
{
...
System.out.println("Cond is false.");
}
...
在没有类似c++中
#ifdef _DEBUG
#endif
的时候我们只有在结束的时候把这段注释掉:
if (cond)
{
...
//System.out.println("Cond is true.");
}
else
{
...
//System.out.println("Cond is false.");
}
...
这是非常繁琐而且容易出错的。
一种解决方法是提供一个OutPutWriter对象,它可以把需要的调试信息输出。如果你不需要输出的时候把这个对象置为null:
// Some class:
public class MyClass
{
private OutputWriter log = null;
public MyClass() {}
public MyClass(OutputWriter log)
{
this.log = log;
}
// in the body of some method:
...
if (cond)
{
...
if (log != null)
log.write("Cond is true.\n");
}
else
{
...
}
...
if (log != null)
log.write("Cond is false.\n");
}
}
这样做虽然可以避免在发布版本不输出调试信息。但还是有几个缺点无法避免:
a. 你必须用if语句段来确保对OutputWriter对象是否为null进行处理。
b. 没有一个一致的方法来处理得到的StringWriter对象。
这个时候就可以考虑使用NullObject模式了。
不需要输出调试信息的时候我们用NullLog来替代RealLog,改一处就可以了,而且使得客户端可以一致的对待所处理的对象,因为NullLog和RealLog都继承于同一接口.
3.另外我们写代码的时候经常是
IMotionStreategy MotionWay = null;
…
If (MotionWay == null)
{
Throw new NullReferenceException ();
}
MotionWay.Run();
若有一个NullObject我们就可以
IMotionStreategy MotionWay = NullMotion.CreateInstance();
…
MotionWay.Run();
完全可以不必当心会出现null的情况,程序中的null完全由Null Object对象代替。在Java和C#里面似乎是没有null的异常了。注意,由于C++中所有变量并非为引用,所以还是可能出现指针异常,而且在C++中没有垃圾收集,构建的NullObject对象按道理不能被释放。
代码片段(Java):
// 感谢网友Goingmm 提供
public interface Person {
/**
* 让这个人喊叫指定的信息(message)
* @param message
* @return
*/
public String shout(String message);
/**
* 1) 定义一个匿名内部类
* 2) 这样做能确保在当前JVM中只有NULL的唯一实例
* 3) 从效率和编码的优雅角度看,采用此策略都是可取的
*/
public static final Person NULL = new Person(){
public String shout(String message) {
return "--对不起!我是NULL对象,我不会喊叫.";
}
};
}
测试代码:
public class TestNullObject {
public static void main(String[] args) {
// ①取得这个特殊的NULL对象
Person obj = getPerson();
// ②调用接口中提供的方法
System.out.println(obj.shout("我是中国人!"));
}
public static Person getPerson() {
return Person.NULL;
}
}
总结:
Null Object模式中的Null Object总得来说并不是要求你必须以空语句段来实现,只不过空语句段是一个很常见得实现方式。它仅仅说明Null Object是个默认的实现对象。这个对象并没有作业务逻辑处理,是意义上的空实现。当然如果它功能有了一定意义那就有可能有背这个模式的初衷了。由于默认的实现对象常常是一样的,所以Null Object可以用Singleton来实现。