The Inner Drive Extensible Architecture™ contains a very useful namespace called Quantitative that contains classes and interfaces to use in measuring things. Essentially, there is a trio of interfaces, IQuantity, IUnit, and IPhenomenon, that allow you to create and convert any kind of measurements. The principal implementation of IQuantity is the Numeric structure.
To convert feet to meters, you do this:
IQuantity feet = new Numeric(100, new Foot());
IQuantity meters = quantity.ConvertTo(new Meter());
Even though that looks simple, it has always troubled me. I've realized in the last couple of days that I got the abstractions wrong.
In any object model, you want to work with the most convenient abstractions. It helps if the resulting code looks like English (or whatever your native language is). Quantity, Unit, and Phenomenon are, indeed, abstractions, but they're not the right ones. Here's how I know.
First, the following compiles fine, but throws an InvalidOperationException when executed:
Numeric feet = new Numeric(100, new Foot());
Numeric fahrenheit = new Numeric(100, new Fahrenheit());
Numeric hotFeet = feet + fahrenheit;
Numeric is too abstract, you see.
Also, getting feet to display as, say, kilometers becomes very annoying:
Numeric feet = new Numeric(10000, new Foot());
Numeric meters = (Numeric)feet.ConvertTo(new Meter());
// "3048 m" displayed
// Numeric.Unit exposes a Unit which may or may not be metric
IExponential exponent = (IExponential)meters.Unit;
exponent.Exponent = MetricExponent.Kilo;
// "3.048 km" displayed
The Numeric structure doesn't care about the metric exponent of its Unit member. Why should it? It contains a value and a unit, and if you add two Numeric objects that use the same Unit, you get exactly what you'd expect.
But why should the Unit care what its exponent is? Now, when converting to or from other Units, it has to take that extra piece of information into account, or the conversions will be off by orders of magnitude.
There are many other problems and annoyances with the Quantitative namespace, which took me months to tease out. But this morning, on the El, I cracked the code (as my dad would say).
Tell me, doesn't this make a lot more sense?
Length meters = new Length(3000, new Meter());
// Alternate syntax using the implicit operator:
// Length meters = 30;
// "3000 m" displayed
Length feet = meters.ConvertTo(new Foot());
// "9842.52 ft." displayed
// "9842.52 ft." displayed; no exception; simply ignored the exponent
// "3 km" displayed
And, of course, our earlier example wouldn't even compile:
Length meters = 30;
Temperature fahrenheit = meters.ConvertTo(new Fahrenheit());
// Won't compile
I'll be refactoring this soon. Everything else I'm building depends on it. Watch this blog for a link to the new demo.