I l@ve RuBoard |
5.24 Implementing the Null Object Design PatternCredit: Dinu C. Gherman 5.24.1 ProblemYou want to reduce the need for conditional statements in your code, particularly the need to keep checking for special cases. 5.24.2 SolutionThe usual marker for "there's nothing here" is None, but we may be able to do better than that: class Null: """ Null objects always and reliably "do nothing." """ def _ _init_ _(self, *args, **kwargs): pass def _ _call_ _(self, *args, **kwargs): return self def _ _repr_ _(self): return "Null( )" def _ _nonzero_ _(self): return 0 def _ _getattr_ _(self, name): return self def _ _setattr_ _(self, name, value): return self def _ _delattr_ _(self, name): return self 5.24.3 DiscussionAn instance of the Null class can replace the primitive value None. Using this class, you can avoid many conditional statements in your code and can often express algorithms with little or no checking for special values. This recipe is a sample implementation of the Null Object design pattern (see "The Null Object Pattern", B. Woolf, Pattern Languages of Programming, PLoP 96, September 1996). This recipe's Null class ignores all parameters passed when constructing or calling instances and any attempt to set or delete attributes. Any call or attempt to access an attribute (or a method, since Python does not distinguish between the two and calls _ _getattr_ _ either way) returns the same Null instance (i.e., self, since there's no reason to create a new one). For example, if you have a computation such as: def compute(x, y): try: "lots of computation here to return some appropriate object" except SomeError: return None and you use it like this: for x in xs: for y in ys: obj = compute(x, y) if obj is not None: obj.somemethod(y, x) you can usefully change the computation to: def compute(x, y): try: "lots of computation here to return some appropriate object" except SomeError: return Null( ) and thus simplify it as: for x in xs: for y in ys: compute(x, y).somemethod(y, x) Thus, you don't need to check whether compute has returned a real result or an instance of Null. Even in the latter case, you can safely and innocuously call on it whatever method you want. Python calls _ _getattr_ _ for special methods as well. This means that you may have to take some care and customize Null to your application's needs, either directly in the class's sources or by subclassing it appropriately. For example, with this recipe's Null, any comparison between Null instances, even a==a, returns a Null instance and evaluates as false. (Note that we've had to explicitly define _ _nonzero_ _ for this purpose, since _ _nonzero_ _ must return an int.) If this is a problem for your purposes, you must define _ _eq_ _ (in Null itself or in an appropriate subclass) and implement it appropriately. Similar delicate considerations apply to several other special methods. The goal of Null objects is to provide an intelligent replacement for the often-used primitive value None in Python (Null or null pointers in other languages). These "nobody lives here" markers are used for many purposes, including the important case in which one member of a group of otherwise similar elements is special. Usually, this usage results in conditional statements to distinguish between ordinary elements and the primitive null (e.g., None) value, but Null objects help you avoid that. Among the advantages of using Null objects are the following:
To cope with the disadvantage of creating large numbers of passive objects that do nothing but occupy memory space, the Null Object pattern is often combined with the Singleton pattern (see Recipe 5.22), but this recipe does not explore that combination. 5.24.4 See Also"The Null Object Pattern", B. Woolf, Pattern Languages of Programming, PLoP 96, September 1996, http://www.cs.wustl.edu/~schmidt/PLoP-96/woolf1.ps.gz. |
I l@ve RuBoard |