GSharper

Wednesday, 28 March 2007

Refactoring to Null Object

On my holiday, I was reading the book refactoring to patterns and I came to a topic called "Introducing Null Object".

It seemed to be extremely promising for some duplicate code I wrote some time ago. Let me explain what the problem is.

I had written code to do some actions on a logfile. Like writing, reading and more similar actions.

The log class looked like this:

public class Log
{


StreamWriter textWriter;

public Log()
{
}

 

public void Write(string logMessage)
{
      textWriter = new StreamWriter(@"<path>\log.txt");
      textWriter.WriteLine(logMessage);
}

 

public string Read()
{
      return "Some log entries";
}

 

public void DoSomeLogAction()
{}

 

public void DoSomeOtherLogAction()
{}


}

Then I created the following service LogService:

public class LogService
{


Log _log;

public LogService(){}

 

public LogService(Log log)
{
      _log = log;
}


public void WriteToLog(string logMessage)
{
        if (_log != null)
       {
                this._log.Write(logMessage);
       }
       else
      {
              DoSomethingElse();
      }
}

public string ReadFromLog()
{
       string result="";

       if (_log != null)
      {
            result = _log.Read();
      }
      else
     {
            DoSomethingElse();
     }
     return result;

public void SomeLogAction()
{
      if (_log != null)
      {
            _log.DoSomeLogAction();
      }
      else
     {
            DoSomethingElse();
     }
}

 

public void SomeOtherLogAction()
{
      if (_log != null)
     {
           _log.DoSomeOtherLogAction();
     }
     else
    {
           DoSomethingElse();
    }
}

 

private void DoSomethingElse()
{}


}

We can see that there's a lot of duplicate code in it. Because of the possibility that logging is not enabled, we repeatedly have to test against the null value. If we don't do this, at some point in time we will definitely going to receive some exceptions.

Isn't it possible to reduce the duplicate code? Yes it is. By using refactoring to Null Object. This is what we have to do.

First of all we extract an interface from the Log Class. Like this:

public interface ILog
{
      void Write(string logMessage);
      string Read();
      void DoSomeLogAction();
      void DoSomeOtherLogAction();
}

 We let Log implement this interface.

Then we create a new class NullLog and let it also implement the ILog interface. But, the difference with the Log class is that NullLog takes care of the actions when a null value is passed to the service. Like this:

public class NullLog : ILog
{
      public NullLog()
     {}

      public void Write(string logMessage)
      {
            DoSomethingElse();
      }

      public string Read()
      {
            string result=""
            DoSomethingElse();
            return result;
      }

      public void DoSomeLogAction()
      {
            DoSomethingElse();
      }

      public void DoSomeOtherLogAction()
      {
            DoSomethingElse();
      }

      private void DoSomethingElse()
      {}

}

It's also common to let the methods do nothing at all.

Now comes the fun part. Change the LogService class to remove redundant code. Like this:

public class LogService
{


Log _log;

public LogService(){}

 

public LogService() : this(new NullLog()){}

public LogService(Log log)
{
      _log = log;
}


public void WriteToLog(string logMessage)
{
        if (_log != null)
       {
                this._log.Write(logMessage);
       }
       else
      {
              DoSomethingElse();
      }
}

public string ReadFromLog()
{
       string result="";

       if (_log != null)
      {
            result = _log.Read();
      }
      else
     {
            DoSomethingElse();
     }
     return result;

public void SomeLogAction()
{
      if (_log != null)
      {
            _log.DoSomeLogAction();
      }
      else
     {
            DoSomethingElse();
     }
}

 

public void SomeOtherLogAction()
{
      if (_log != null)
     {
           _log.DoSomeOtherLogAction();
     }
     else
    {
           DoSomethingElse();
    }
}

 

private void DoSomethingElse()
{}

}

First of all we changed the default constructor to pass an instance of the NullLog class to the overloaded constructor. So whenever the Service is instantiated without a Log object, everything will be delegated to the NullLog object.

Because of this change, all the null testing is redundant and can be removed.

Finally the private method DoSomethingElse is no longer necessary and can be safely removed.

The new ServiceLog class now looks like this:

public class LogService
{
      private ILog _log;

      public LogService() : this(new NullLog()){}

      public LogService(ILog log)
      {
            _log = log;
      }

      public void WriteToLog(string logMessage)
      {
            _log.Write(logMessage);
      }

      public string ReadFromLog()
      {
            return _log.Read();
      }

      public void SomeLogAction()
      {
            _log.DoSomeLogAction();
      }

      public void SomeOtherLogAction()
      {
            _log.DoSomeOtherLogAction();
      }

}

This looks a lot better doesn't it.

Comments are always welcome.

Greetz,

G

Labels: ,

2 Comments:

  • This is a nice example of how to use null objects. There are several examples in the BCL like EventArgs.Empty ...

    However in your example I would like to point out that in OO it is important to have your object in a valid state upon creation. I would remove the parameterless constructor and if a null reference is passed as a parameter to the constructor throw a nullreference exception. Furthermore You could create a private getter that returns the _log object but before returning verify that it hasn't been null dereferenced in another thread.

    By Anonymous Gabriel Lozano-Moran, At 2 April 2007 at 11:20  

  • Gabriël definitely has a point here.

    The refactoring mechanics I followed in my example come from the book of Kerievsky. There he shows an example of a custom mouse eventhandler. This eventhandler needs to receive some data. It's not much data but there is a possibility that the eventhandler isn't instantiated before it's used, resulting in exceptions.

    That's why I used the parameterless constructor and the null ref passing.

    But, as Gabriël always says, there is always an IF :)

    So let's follow his advice and change my code a little bit.

    We declare a static null object:

    private static ILog _nullLog = new NullLog();

    We remove the parameterless constructor. In the other constructor we test the passed ILog parameter for null. If null, throw a NullRefernceException.

    We create a private getter and also test on null.

    Finally we do an encapsulate field on _nullLog:

    public static ILog NullLog
    {
    get
    {
    return _nullLog;
    }
    }

    Now, when Logging is disabled LogService will be instantiated like this

    LogService nullLogService = new LogService(LogService.NullLog);

    Greetz,
    G

    By Blogger GSharp, At 3 April 2007 at 05:31  

Post a Comment

Subscribe to Post Comments [Atom]



<< Home