In his book, Analysis
Patterns: Reusable Object Models, Martin Fowler documents a
number of useful patterns for storing quantities
such
as a length, weight, speed, pressure or temperature. While Martin's
examples and arguments are predominantly drawn from the medical
profession, these patterns are generally applicable wherever values can
be expressed in different units of
measure. The following examines the implementation of some of
these patterns through different examples, adding detail, making
additional observations, and discussing some alternatives.
Of course, the simplest way to store a quantity is to represent that
quantity as
an attribute of a class. For example, a real estate
application that records the living space as floor area of a residence
could represent this as a suitably-named integer attribute of a
Residence class. Similarly, it
might store the amount of land on which a residence stands (lot size)
in another attribute of the same
class (see figure 1).
Figure
1: Quantities as attributes (member fields, instance variables) of a
class
As Martin
Fowler points out, this approach can cause problems
because the attributes contain no indication of the units that the
quantities were measured in. The floor area and lot size are likely to
be
expressed in metres squared in Europe but in square feet and acres
respectively in the USA. Without any explicit indication of what a
quantity is measured in, programmers can make bad assumptions, as history
has shown.
One very simple solution is to expand the name of the attribute to
include
the
units of measure and define accessor methods for each unit of measure
we are interested in. Without the extra accessor methods, different
users of
the class might need to build code to convert from one unit to the
other. We could end up with duplicated unit conversion code and a
greater chance of errors in that code. Encapsulating
the conversion logic in the Residence
class eliminates this problem.
Figure
2: Units as part of attribute names and accessor methods
For very simple cases, expanding names and adding extra accessor
methods might be sufficient. However,
it quickly becomes laborious in a problem
domain that has numerous
concepts with a number of measured quantities. A system for
recording results
at a major
athletics games such as the Olympics is one example:

Figure
3: Classes for a very simple field event module
Field events are all about distances, either
jumped or thrown. Figure 3 shows a set of classes for a simplistic
module for field event results:
Adding extra accessor methods for each distance attribute and
the ability to convert between metres, centimetres, millimetres, yards,
feet, and
inches
to both of the pink
classes in the model is tedious and duplicates unit conversion code in
both classes.
The duplicated code problem could be addressed by creating a
'utility
class' with a series of class-level (static) methods that convert
between
the different units that an event might be recorded in. After, sending
the designer of such a scheme to the bottom of the object-oriented
design class, we look for a
more elegant answer.
Note 1: the colours and stereotypes in figure 3 come from Peter Coad's 'modeling in color' technique.
Note
2: the Number type used for
the attributes is a place-holder for a suitable decimal type of the
programming language being used.
Note 3: at this level of abstraction, I use public attributes in UML classes to represent Java bean/C# style properties
The solution, as it so often is in object-oriented software, is
more encapsulation. Martin Fowler suggests modeling quantities as shown
in
figure 4.

Figure
4: Quantity and Units with ConversionRatio
In figure 4, the Quantity
class encapsulates the amount and units of
a quantity and takes on the
responsibility of converting a quantity into one with different units.
To perform this conversion, an object of the Quantity class looks for
an object of the ConversionRatio
class that
links its Unit object to the
desired Unit object. For
example, if a
Quantity object is linked to
the metre
object of the Unit class, and
we need the quantity in inches, the convertTo()
method
needs to locate an object of the ConversionRatio
class that is
associated with both the metre and
the inch object of the
Unit class.
If no
suitable ConversionRatio
object exists, it may be possible to combine
multiple ConversionRatio
objects to provide a sequence of
multiplications
that will convert between the two units. For example, if when
converting a quantity from centimetres to feet, the convertTo()
operation cannot find an object of the of the ConversionRatio class
with links to both centimetres and feet, it may be able to find one
that links from centimetres to metres and another that links metres to
feet. This requires some level of sophistication in the code doing the
look-up.
A simpler alternative is for objects of the Unit class to each contain the
multiplier value needed to convert to a specific unit. This specific
unit could be
the relevant SI
base
unit; metre for distance, kilogram for weight, second for time,
and
so on. Now, instead of the overhead
of finding the relevant ConversionRatio
object or sequence of ConversionRatio
objects, converting a quantity from one set of units to another
is
three simple steps:
Note
1: It is invalid to
convert between units with different SI base units because you cannot
convert between two different types of measurement e.g. convert between
a length and a weight.
Note 2: the multiplier
must never be zero to avoid
division by zero errors. In fact, the multiplier must be a positive
number.
To enable us to check that two quantities share the same SI base units, we simply link each object of the Unit class to the object of the Unit class representing the relevant SI base unit (see figure 5).
In this alternative to the model in figure 4, we have completely
removed the need for the ConversionRatio
class; a simpler model that still does the job (see figure 5). Peter
Coad would approve!
While a simple ratio can be used to convert between
many units of measure, ( e.g. inches to millimetres, millimetres to
metres, etc) a more sophisticated
function is required to convert between items like Celsius and Fahrenheit. To
convert from Celsius to Fahrenheit, you multiply by 1.8 and then add
32. In mathematical terms, converting between Celsius and
Fahrenheit
requires a function of the form y=mx+c, known as a linear function.
To go the other way, from Fahrenheit to Celsius you subtract 32 and
divide the result by 1.8. This requires rearranging the formula into
x=(y-c)/m. Therefore, if in addition to the
multiplier, m,
that we used for converting by a simple ratio, we also keep a
constant, c, in the Unit
class we can convert between units
like Celsius and Fahrenheit that require a linear function. Our model
now looks
something like figure 5.

Figure 5: Alternative
Quantity/Unit model
If, in addition to linear functions, we
need
to support different kinds of function such as logarithmic or
exponential functions, a strategy pattern
starts to look an attractive.
Note:
my first major
software development project involved plenty of linear function
conversions from raw measurements of temperatures and pressures into
the units appropriate for the control room displays of an oil-fired
power station.
What happens if we want to add or subtract quantities? The first
thing to do is
check if both quantities are in the same units. If they are then we
simply add or subtract the values in the amount attributes of each
Quantity object. If the units
are different, we need to convert either
one or
both of the quantities so that we do have two quantities with the same
units. So that we
do not have write the code for the checks and conversions everywhere we
want to add or subtract quantities, we make it the responsibility of
the Quantity class, defining
operations to add or subtract another
Quantity object (see figure 6).
But what units should the result of one of these operations be
in? If I ask what is 6 feet plus 6 inches, the natural
answer is 6 and a half feet, not 78 inches, or 2.1667 yards. Therefore,
we could make results of our new addition and
subtraction operations the larger of the two
units but that means dong extra work to determine which of the two is
the larger. An easier alternative is to always express the answer in
the units of the object that we invoke the operation on. In other
words, if we say A.add(B)
then we express the answer in A's
units but if we say B.add(A)
we express the answer in B's
units. This is a more flexible for addition than subtraction because
the answer is the same whether we add A
to B or B to A. Unfortunately, that is not the
case for subtraction. Despite this restriction, it is simpler if we
apply the same rule for subtraction and return the result in the units
of the object that we invoke the operation on. Therefore, A.subtract(B) returns a result in
the units of A.
What about multiplication and division? It is nonsense to talk about
multiplying or dividing one quantity by another; 6 pounds multiplied by
3 ounces is meaningless. It only makes sense to talk about multiplying
or dividing a quantity by a number. For example, 3 ounces multiplied by
6 is 18 ounces. There is only one quantity involved so it is
easiest to express the result of the operation in the same units as
that quantity. However, this might look a little unnatural because we
naturally change units when amounts become too large or too small. For
example, four centimetres multiplied by a hundred is most naturally
expressed as four metres, not four hundred centimetres. We could add
logic to the Quantity class
to select the most appropriate units for
the result of a maths operations but for most situations this is likely
to be
straying into the realm of over-engineering.
We can define comparison operations for the Quantity class too.
These are simpler to define because they return either a boolean or an
indication
of greater, less than or equal to; no units to choose for the result.
Equals can be a problem for amounts where rounding takes place during
conversion between units or arithmetic. Therefore, in addition to the
basic equals operation that compares values exactly, the Quantity class typically needs
an
isWithin() operation that
declares two quantities as equal if they are within a specified
tolerance amount of each other.
None of the models we have seen so far fully caters for non-metric systems because none caters for expressing quantities in non-decimal format such as 8 hours, 6 minutes and 14 seconds, or 6 miles, 12 yards, 5 feet and 6 inches, or 4 pounds 2 ounces. To do this, each value would need to comprise of a sequence of one or more Quantity objects with additional logic to manage the correct use of Unit objects. In addition, the basic mathematical operations become much more complicated because they need to deal with this sequence of Quantity objects and non-decimal values. Now our model needs to look something like figure 6:

Figure 6:
Non-decimal Quantities
In figure 6, a quantity now may be made up of a sequence of Quantity
objects where each subsequent object in the sequence is in a sub-unit
of
the previous Quantity's
units. The Unit class defines
a similarly linked
sequence of Unit objects to
constrain the immediate sub-unit and
super-unit for a particular Unit
object. For example, the mile
has an immediate sub-unit of yard.
This in turn, has an
immediate super-unit of mile
and an immediate sub-unit of foot.
Additionally, each Unit
object knows how many of its sub-units
there are in one of its own units and how many of itself there are in
one
of its super-units. This additional knowledge is needed to perform the
basic maths operations; to be able to add 2 feet and 11 inches to 2 yards, 2 feet and 9 inches, we
need to know that there are 12 inches in a
foot and 3 feet in a yard,
Note:
these additional values are
the same as m would be in y=mx+c if we were converting values
between sub-units x and units
y; c is obviously always zero in these
cases.
Again, for most circumstances, fully supporting non-decimal
quantities is
likely to be over-engineering the solution. There are very good reasons
why we use
decimal numbers and why most of the world has officially adopted the metric system
albeit with some popular remnants of old units (UK pubs and bars still
sell beer in pints, not liters).
Decimal
numbers and metric quantities are so much easier to work with in most
software situations.
In Analysis Patterns, Martin Fowler proposes two models that explicitly support compound units. Figure 7 shows his preferred shape but with additional link to the relevant SI base unit introduced in figure 5
Figure 7: Compound units modeled explicitly
The Unit class becomes an abstract class with two subclasses:
Note
1: It is still invalid to
convert between units with different SI base units because you cannot
convert between two different types of measurement e.g. convert between
an acceleration and a pressure.
Note 2: the multiplier
must still never be zero to avoid
division by zero errors. In fact, the multiplier must be a positive
number.
Note
3: a more sophisticated algorithm ignores units that appear in
the same collections of both the current and desired units. For example
when converting between miles per hour and kilometres per hour, the
hours can be ignored because the division for the hour when converting
to the SI base unit is cancelled by the multiplication for the hour
when
converting to the desired units.
Note
4: It is still valid to support a linear function instead of a
straight ratio as described in figure 5 or use a strategy pattern
instead.
Armed with our Quantity
and Unit pattern, we can
rework our field event module's classes. For field events, we are only
interested in distance, so our Quantity
can be expressed more specifically as a class called Distance. In addition, our units
are all some sort of distance, so all have the same SI base unit, the
metre. This means we can simplify the Unit
class further. Instead of each instance holding a reference to
their related SI base unit object, we can reference the metre object
from a class-level (static in
C++, Java, and C#) variable. The checks for quantities being related to
the same SI base unit object in the conversion, comparison and
arithmetic operations are redundant. Finally, all the units convert to
metres via a straight forward ratio; full linear function support in
unit conversions is not needed. Figure 7 shows the simpler, more
specific model.

Figure 7: Application of
Quantity pattern produces Distance and Unit classes
This sort of tweaking is typical when applying a pattern. Patterns
should not be applied blindly without thought. On the other hand, a
pattern should only be tweaked by someone that fully understands that
pattern so that the modifications do not undermine the whole point of
using it.
Now that we have our Distance and Unit classes we simply replace all the Number attributes in the model from figure 3 with attributes of type Distance. Job done! Time for a cup of tea! Well, maybe. It is true that if we want to retain the knowledge of what units each quantity was measured in, then we need to use attributes of type Distance because each one has its own associated Unit object. Martin Fowler argues that this is exactly what is required for a medical software system. However, as we have seen, it is more work to do comparisons and maths operations if the values involved are in different units. For our simplistic field event system, we are not concerned with remembering that the official from USA measured in feet and inches and the French official in centimetres. We have decided we want everything converted to and stored in metres, the SI base unit of length.

Figure
8: Attributes of type Distance
In this case, we can leave the private attributes as type Number but insist that the public operations of the classes be defined in terms of quantities (see figure 9). Now:

Figure 9: Number on the inside, Quantity from the outside
To summarize:Up until this point, we have only modeled field events because these
all involved recording distances, either thrown or jumped. Other types
of event are measured in time, points, or weight. These are still
quantities but they are not distances. We could support these events by
defining specific quantity and unit classes for time, for points, and
for weight. Obviously, these pairs of classes would be very similar. It
is tempting to use inheritance or generics to express this commonality,
we quickly become concerned with issues of co
and contra-variance. While a solution based on inheritance or
generics may provide some compile time type safety, a simpler approach
is to return to a more general implementation of the quantity pattern,
make our FieldEvent and FieldEventParticipation classes
more general, and define a few more Unit objects (see figure 10).

Figure
10: More general model to support more types of athletics events
This does introduce two problems:
Another kind of quantity is
money. The units of measure for money are currencies. The big
difference between
money and the quantities consider so far is that the conversion ratio
or exchange rate between currencies moves. The conversion ratio between
inches and feet is always twelve. The value of exchange rates
fluctuate, soar and dive. Different exchange rates apply according to
circumstances. Are you buying or selling? How much are you buying or
selling? Are you buying or selling now or for in the future? How much
money is available at a particular rate.
Because the rate moves, and different rates may be applied,
simply encapsulating 'the rate' inside the unit class does not work.
Either we
need to supply the relevant exchange rate to the currency class when we
convert between currencies or we supply the currency to something else
that does the conversion.

Figure 11: Money, Currency and
Exchange Rates
Figure 10 shows some of the changes
typically needed in serious enterprise software involving exchange
rates. Three new classes have appeared in comparison to figure 5:
Therefore, it is typically cleaner for the ExchangeRate class to be
responsible for converting one MonetaryValue
object into another instead of MonetaryValue
(quantity) class.
Additionally, in some cases, an extra attribute may be
required that holds the amount of funds still available at that rate.
This can get complicated if users of the class need to check if a
certain amount is available before confirming the conversion. We
essentially have a typical reservation or stock-on-hand problem;
someone else could reduce the amount available after we have checked
but before we have confirmed a conversion.
ExchangeRateSchedule
objects represent a collection of exchange rates
from a particular source that are applicable for a
particular period of time.
ExchangeRateSchedule
objects are often responsible for finding the best ExchangeRate object for a requested
conversion. Depending on the situation, this might be constrained to
the collection of rates that it knows about, or it may be able to
derive a new ExchangeRate
object from a combination. Where limited funds are available for
each rate, deriving new ExchangeRate
objects becomes even more complicated.
ExchangeRateBoard
objects represent a regular source of exchange rates. It might
represent a list from a financial newspaper, the board displayed in
bank branches or shops that accept multiple currencies, etc. Depending
on the circumstances, the board is responsible for producing either a
schedule of ExchangeRate
objects, or specific ExchangeRate
objects on demand. In the latter case, the ExchangeRateSchedule class may not
be needed and the ExchangeRate
class takes on the responsibility of tracking the effective interval
for the its rate.