Consider the following code: Employee e = DB.GetEmployee("Bob"); if (e != null && e.IsTimeToPay(today)) e.Pay(); We ask the database for an Employee object named "Bob". The DB object will return null if no such object exists. Otherwise, it will return the requested instance of Employee. If the employee exists and is owed payment we invoke the pay method. We've all written code like this before. The idiom is common because, in C-based languages, the first expression of the && is evaluated first, and the second is evaluated only if the first is true. Most of us have also been burned by forgetting to test for null. Common though the idiom may be, it is ugly and error prone. We can alleviate the tendency toward error by having DB.GetEmployee throw an exception instead of returning null. However, try/catch blocks can be even uglier than checking for null. We can address these issues by using the NULL OBJECT pattern.[1] This pattern often eliminates the need to check for null, and it can help to simplify the code.
Figure 25-1 shows the structure. Employee becomes an interface that has two implementations. EmployeeImplementation, the normal implementation, contains all the methods and variables that you would expect an Employee object to have. When it finds an employee in the database, DB.GetEmployee returns an instance of Employee-Implementation. NullEmployee is returned only if DB.GetEmployee cannot find the employee. Figure 25-1. Null Object pattern
NullEmployee implements all the methods of Employee to do "nothing." What "nothing" is depends on the method. For example, one would expect that IsTimeToPay would be implemented to return false, since it is never time to pay a NullEmployee. Thus, using this pattern, we can change the original code to look like this: Employee e = DB.GetEmployee("Bob"); if (e.IsTimeToPay(today)) e.Pay(); This is neither error prone nor ugly. There is a nice consistency to it. DB.Get-Employee always returns an instance of Employee. That instance is guaranteed to behave appropriately, regardless of whether the employee was found. Of course, in many cases, we'll still want to know whether DB.GetEmployee failed to find an employee. This can be accomplished by creating in Employee a static readonly variable that holds the one and only instance of NullEmployee. Listing 25-1 shows the test case for NullEmployee. In this case, "Bob" does not exist in the database. Note that the test case expects IsTimeToPay to return false. Note also that it expects the employee returned by DB.GetEmployee to be Employee.NULL. Listing 25-1. EmployeeTest.cs (partial)
The DB class is shown in Listing 25-2. Note that, for the purposes of our test, the GetEmployee method simply returns Employee.NULL. Listing 25-2. DB.cs
The Employee class is shown in Listing 25-3. Note that this class has a static variable, NULL, that holds the sole instance of the nested implementation of Employee. NullEmployee implements IsTimeToPay to return false and Pay to do nothing. Listing 25-3. Employee.cs
Making NullEmployee a private nested class is a way to make sure that there is only a single instance of it. Nobody else can create other instances of the NullEmployee. This is a good thing, because we want to be able to say such things as: if (e == Employee.NULL) This would be unreliable if it were possible to create many instances of the null employee. |