Description


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.

[1] [PLOPD3], p. 5. This delightful article is full of wit, irony, and quite practical advice.

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)

[Test] public void TestNull() {   Employee e = DB.GetEmployee("Bob");   if (e.IsTimeToPay(new DateTime()))     Assert.Fail();   Assert.AreSame(Employee.NULL, e); }

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

public class DB {   public static Employee GetEmployee(string s)   {     return Employee.NULL;   } }

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

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()     {     }   } }

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.




Agile Principles, Patterns, and Practices in C#
Agile Principles, Patterns, and Practices in C#
ISBN: 0131857258
EAN: 2147483647
Year: 2006
Pages: 272

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net