Monday, 2 February 2009

Null Object - the third player

In my previous post I wrote about my considerations about the exceptions and null values. I suggested one more possibility - an "error-indicating object", which basically means to return a special, recognizable instance of the returned interface. This approach has been already described as the Null Object Pattern. Returned object is supposed to do nothing. This approach is mostly suitable for mocking up business entities. Here I'd like to extend this pattern to the service level. Let's assume we're working on a word processor which is able to read/write documents in many formats. Of course our word processor is very flexible and uses format plug-ins and we are able to add/remove the formats very easily. What happens when I try to open a document file? For sure the word processor has to choose appropriate format handler. It's very common case when the logic is context dependent and is chosen at runtime. Yes, you're right, that's a well known Strategy Pattern. All right, so let's assume there are the following interfaces defined:
public interface DocumentHandlerSelector {
 DocumentHandler selectDocumentHandlerFor(URI documentURI);
}

public interface DocumentHandler {
  Document read(URI documentURI);
  void write(Document documentToWrite);
}

public interface Document {
  boolean isLoaded();
}
Everything is fine as long as the concrete handler can be explicitly chosen by the DocumentHandlerSelector. But what to do if for some reason the application can't pick the correct one? For sure it should show a gentle information about the problems with opening the document in the message box. It can be achieved in two equivalent ways. The first way - throw the exceptions:
public Document open(final URI documentURI) {
 Document document = null;
 try {
     DocumentHandler handler = documentHandlerSelector
         .selectDocumentHandlerFor(documentURI);
     document = handler.read(documentURI);
 }
 catch (UnknownDocumentFormatException e) {
     showMessageBox("The document cannot be opened");
 }
 return document;
}
The second way - use the null objects (for both DocumentHandler and Document):
public Document open(final URI documentURI) {
 DocumentHandler handler = documentHandlerSelector
     .selectDocumentHandlerFor(documentURI);
 Document document = handler.read(documentURI);
 if (!document.isLoaded()) {
      showMessageBox("The document cannot be opened");
 }
 return document;
}
And here are the implementations to make the second example easier to understand:
public class UnsupportedDocumentHandler 
    implements DocumentHandler {

 private static final Document UNSUPPORTED_DOCUMENT 
     = new UnsupportedDocument();

 Document read(final URI documentURI) {
     return UnsupportedDocumentHandler.UNSUPPORTED_DOCUMENT;
 }
}

class UnsupportedDocument implements Document {
 boolean isLoaded() {
     return false;
 }
}
I like the NullObject approach for not returning plain null from the open() method. And what do you prefer?

0 komentarze: