Saturday, October 21, 2006

Objects: Not Just For Breakfast Anymore

Objects in OO languages are a powerful concept. They encourage collecting data and behaviour in highly cohesive bundles that effectively communicate a concept to a human. Effectively a block of memory, objects are technically very simple. In the managed world, we don't even need to worry about the memory because a garbage collector will clean up after us. Yet despite their power and simplicity objects are rarely employed to their full potential. Most applications contain a few coarse grained objects and a bedlam of procedural artifacts such as constant strings, integers and enumerations floating out in the global ether. So why are we as developers so hesitant to create new objects?

While there is nothing wrong with using enumerations in applications, there is really nothing gained from using them aside from the syntactic pattern. An enumeration is simply a wrapper around an integer (or any other primitive) to help communicate a concept to a human reading the code. The fact that it is an integer is irrelevant (at least it should be, but I'll get to that later). Rather than masquerading an integer as a type or concept, why not pass around an object? Try replacing your enumerations with something like this:

public abstract class UnitOfMeasure

{

public static readonly object Meters = new object();

public static readonly object Feet = new object();

}


This class is abstract so it can never be instantiated and the public fields inside of it are immutable. Now it is an immutable memory address that we are passing around rather than an integer value. We can do things like:

public class Quantity

{

private decimal value;

private object units;

public void DoSomething()

{

if(units.Equals(UnitOfMeasure.Meters))

{

// do something

}

}

}


But you'll notice because we are passing around a naked object we lose some type safety. By giving a constructor to the class we gain a powerful type safe constant:

public class UnitOfMeasure

{

public static readonly UnitOfMeasure Meters = new UnitOfMeasure();

public static readonly UnitOfMeasure Feet = new UnitOfMeasure();

private UnitOfMeasure()

{

}

}


Now our Quantity class communicates that units are a UnitOfMeasure:

public class Quantity

{

private decimal value;

private UnitOfMeasure units;

public void DoSomething()

{

if(units.Equals(UnitOfMeasure.Meters))

{

// do something

}

}

}


Applications are also littered with constant strings. Often the strings are abused and conditional logic is based on the value of those constant strings. In a recent application I caught myself in a stringy trap by passing around units as naked strings. While this worked when we were only dealing with length units as soon as temperature units were added to the codebase the application got messed up. Temperature strings were being passed in where length strings were expected and caused some confusing and difficult to debug problems. So I replaced my string constants with objects:

public class LengthUnit

{

public readonly static LengthUnit Metric = new LengthUnit();

public readonly static LengthUnit Imperial = new LengthUnit();

private LengthUnit()

{

}

}


And a similar class for temperature units. This introduced some type safety and helps to communicate what exactly is expected. After this refactoring I couldn't even compile if I was passing in the wrong unit of measure type.

This is all fine and good in the simple case where constants are passed around to identify types. However, we can also get into situations where we've abused enumerations and strings to the point where a simple object replacement won't suffice. For example, in an enumeration the integer value means more than just a type, its also used as a value in a calculation. Or perhaps the string constant is used to display something to the user.

Well, fortunately it is this precise situation where objects really shine. Since we can associate multiple pieces of data and behaviour with an object there is no need to abuse strings or enumerations anymore. Lets look at some code:

public enum UnitsOfMeasure

{

Meters = 3.28m,

Feet = 0.3048m

}

public class SomeClass

{

public decimal AddOneMeterTo(decimal value, UnitsOfMeasure units)

{

decimal oneMeter = 1m;

if(units.Equals(UnitsOfMeasure.Feet))

{

oneMeter += oneMeter*Convert.ToDecimal(UnitsOfMeasure.Feet);

}

return value + oneMeter;

}

}


In this scenario I want to add 1 meter to some value. If the original value is in feet, however, I want to convert 1 meter to the imperial equavalent. Fortunately, I've created an enumeration that contains the conversion from meters to feet. Right?

Well, after re-reading this code I felt shame. My fellow developers won't know what that mysterious 3.28 and 0.3048 mean. Plus conversion is a common method that will likely be applied in other places in our codebase so there's potential for code duplication. I also don't like the conditional around feet and only applying the conversion if it is feet. So, I opted to refactor to a more OO solution as follows:

public class UnitsOfMeasure

{

public static readonly UnitsOfMeasure Meters = new UnitsOfMeasure(1m);

public static readonly UnitsOfMeasure Feet = new UnitsOfMeasure(0.3048m);

private readonly decimal conversionFactor;

private UnitsOfMeasure(decimal conversionFactor)

{

this.conversionFactor = conversionFactor;

}

public decimal ConvertTo(decimal value, UnitsOfMeasure convertToUnits)

{

return value*conversionFactor/convertToUnits.conversionFactor;

}

}

public class SomeClass

{

public decimal AddOneMeterTo(decimal value, UnitsOfMeasure units)

{

return value + UnitsOfMeasure.Meters.ConvertTo(1m, units);

}

}


Notice how the conversion behaviour was pushed back into the UnitsOfMeasure class, where it rightfully belongs. Because UnitsOfMeasure now know how to convert to each other I removed the need for the conditional and the potential for code duplication. Finally, UnitsOfMeasure is now a highly cohesive class that communicates to humans that those magic numbers are actually conversion factors.

In summary, finely grained objects can be used as a replacement to string constants and enumerations. Similar data and behaviour can be collected in objects to create highly cohesive classes which helps reduce code duplication and is an effective way to communicate concepts to other developers. Objects are also an effective use of type safety to ensure that the correct constants are being passed around. Starting out with constant objects rather than strings or enumerations also sets you up for simpler refactorings. For example, moving conversion behaviour to the UnitsOfMeasure class would've been a trivial refactoring if we had started with constant objects rather than enumerations. So think twice before introducing another string constant or enumeration into your application and consider the option of employing objects to their full potential.

 
s