In most object-oriented languages, such as Java, references may be null. These references need to be checked to ensure they are not null before invoking any methods, because you can't invoke anything on a null reference. This tends to make code less readable. Instead of using a null reference to convey absence of an object (for instance, a non-existent customer), you use an object which implements the expected interface, but whose method body is empty. The advantage of this approach over a working default implementation, is that a Null Object is very predictable and has no side effects: it does nothing.
It can also be used to act as a stub for testing, if a certain feature, such as a database, is not available for testing.
In the application I am currently building, a product catalog can be searched based on price ranges. For this I created a PriceRangeItem class that is contained within a list provided by a PriceRange class. The idea is to bind the UI against the list to provide a filter for the products based on the price range. The list needs to include an "empty choice" item that, when selected, should not affect the search results. This "empty choice" item is an ideal candidate for a Null Object.
1 public class PriceRangeItem
2 {
3 private readonly int rangeId;
4 private readonly double rangeFrom;
5 private readonly double rangeThru;
6 private readonly string rangeText;
7
8 public static readonly PriceRangeItem Null = new NullPriceRangeItem(0, double.MinValue, double.MaxValue, string.Empty);
9
10 #region Constructors + Destructors
11
12 public PriceRangeItem(int rangeId, double rangeFrom, double rangeThru, string rangeText)
13 {
14 this.rangeId = rangeId;
15 this.rangeFrom = rangeFrom;
16 this.rangeThru = rangeThru;
17 this.rangeText = rangeText;
18 }
19
20 #endregion
21
22 #region Public Members
23
24 public int RangeId
25 {
26 get { return rangeId; }
27 }
28
29 public double RangeFrom
30 {
31 get { return rangeFrom; }
32 }
33
34 public double RangeThru
35 {
36 get { return rangeThru; }
37 }
38
39 public string RangeText
40 {
41 get { return rangeText; }
42 }
43
44 #endregion
45
46 #region NullPriceRangeItem Class
47
48 private class NullPriceRangeItem : PriceRangeItem
49 {
50 public NullPriceRangeItem(int rangeId, double rangeFrom, double rangeThru, string rangeText)
51 : base(rangeId, rangeFrom, rangeThru, rangeText)
52 {
53 }
54 }
55
56 #endregion
57 }
1 if (SelectedPriceRange != PriceRangeItem.Null)
2 {
3 ...
4 }
5
1 public static class PriceRange
2 {
3 private static readonly List<PriceRangeItem> list = null;
4 private const string RangeText = "{0:C} - {1:C}";
5
6 static PriceRange()
7 {
8 list = new List<PriceRangeItem>();
9
10 list.Add(PriceRangeItem.Null);
11 list.Add(new PriceRangeItem(1, 0, 50, string.Format(RangeText, 0, 50)));
12 list.Add(new PriceRangeItem(2, 51, 100, string.Format(RangeText, 51, 100)));
13 list.Add(new PriceRangeItem(3, 101, 250, string.Format(RangeText, 101, 250)));
14 list.Add(new PriceRangeItem(4, 251, 1000, string.Format(RangeText, 251, 1000)));
15 list.Add(new PriceRangeItem(5, 1001, 2000, string.Format(RangeText, 1001, 2000)));
16 list.Add(new PriceRangeItem(6, 2001, 10000, string.Format(RangeText, 2001, 10000)));
17 }
18
19 public static List<PriceRangeItem> All
20 {
21 get { return list; }
22 }
23
24 public static List<PriceRangeItem> InRange(double rangeFrom, double rangeThru)
25 {
26 return All.FindAll(delegate(PriceRangeItem item)
27 {
28 return item.RangeFrom >= rangeFrom && item.RangeThru <= rangeThru;
29 });
30 }
31 }
32