NULL OBJECT 模式
——《敏捷软件开发 原则、模式与实践(c#版)》第25章
描述
考虑如下代码
if (e != null && e.IsTimeToPay(today))
{
e.Pay();
}
我们要从数据库中获取名为“Bob”的Employee对象。如果该对象不存在,DB对象就返回null;否则,就返回请求的Employee实例。如果雇员存在,并且到了他的发薪日,就调用Pay方法。
我们以前都曾经编写过类似这样的代码。代码采用的惯用法很常见,因为在基于C的语言中,&&的第一个表达式会先求值,而仅当第一个表达式为true时才会对第二个表达式求值。大多数人也曾经由于忘记对null进行检查而受挫。该惯用法虽然很常见,但却是丑陋且易出错的。
通过让DB.GetEmployee抛出一个异常而不是返回null,可以减少出错的可能。不过,try/catch块比对null的检查更加丑陋。
可以使用NULL OBJECT 模式来解决这些问题。通常,该模式会消除对null进行检查的需要,并且有助于简化代码。
实现
在(图1-1)中展示了该模式的结构。Employee变成了一个具有两个实现的接口。EmployeeImplementation是正常的实现。它包含了Employee对象被期望拥有的所有方法和变量。当DB.GetEmployee在数据库中找到了一个雇员时,就返回一个EmployeeImplementation实例。仅当DB.GetEmployee在数据库中没有找到雇员是才返回NullEmployee的实例的。
图1-1 NULL OBJECT 模式
NullEmployee实现了Employee的所有方法,方法中“什么也没做”。“什么也没做”的含义和具体的方法有关。例如:有人会期望IsTimeToPay方法实现为返回false,因为根本不会为NullEmployee支付薪水。
使用这个模式,最初的代码可以改为类似这样:
if (e.IsTimeToPay(today))
{
e.Pay();
}
这种做法既不易于出错又不丑陋,并且具有很好的一致性。DB.GetEmployee总是会返回一个Employee的实例。不管是否找到雇员,都可以确保所返回的实例具有合适的行为。
当然,在许多情况下仍难想要知道是否DB.GetEmployee没有找到雇员。在Employee中创建一个持有唯一NullEmployee实例的static readonly变量,就可以达到这个目的了。
DB.CS
public class DB
{
public static Employee GetEmployee( string s)
{
return Employee.NULL;
}
}
Employee.cs
// Employee.cs
using System;
public abstract class Employee
{
public abstract bool IsTimeToPay(DateTime time);
public abstract void Pay();
public static readonly Employee NULL=
new NullEmployee();
private class NullEmployee : Employee
{
public override bool IsTimeToPay(DateTime time)
{
return false;
}
public override void Pay() {}
}
}
使NullEmployee成为一个private内嵌类是一种确保该类只有单一实例的方法。其它任何人都无法创建NullEmployee的其它实例。这非常好,因为我们希望可以这样表达:
如果可以创建无效雇员类的多个实例,那么这种表达方式就是不可靠的。
End.