Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Object-Oriented Dimensional Units


SEP88: OBJECT-ORIENTED DIMENSIONAL UNITS

John Grosberg is a senior staff engineer at Motorola's Government Electronic Group and can be reached at 5842 N 86th St., Scottsdale, AZ 85253.


Object-oriented design (OOD) differs from traditional software design approaches in that OOD assumes that programs are made up of "objects" and "classes" that send "messages" to each other, instead of being made up of procedures and data. OOD emphasizes the structure of a program over its function. When taking the OOD approach, you have to develop the knack of selecting the correct objects and their relevant messages. Selecting objects is not always straightforward since an "object" may not be a physical object--often it is a mental object.

In OOD, an object is a programming entity that has the characteristics of both data and procedures, while a class is a programming construct that defines the common features of a set of objects. These features include both the set of values that a class's objects may have and the set of permissible operations on them. The operations that a class provides do the following: create an object of the class, change the value of an object, read the value of an object, and destroy an object.

Programmers who use object-oriented languages usually refer to the operations as messages. You can relate one class to its subclass as parent to child. The subclass inherits some or all of the capabilities of the parent class and may add new capabilities of its own. If the subclass substitutes an operation for one that it inherited from its parent, making the inherited operation inaccessible, then the new operation is said to override the inherited one. These terms are defined without reference to any programming language because you can apply the principles of OOD to any language. Of course, doing this is easier in some languages than in others. In this article, I will describe how to apply the object-oriented approach to dimensional units in Ada. Dimensional units are used in any application that has to deal with physical quantities. The article also describes how to apply OOD to abstractions in general. Abstractions typically give novice object-oriented designers the most trouble. If you want to review a more conventional approach to implementing dimensional units in Ada, see "Dimensional Data Types," in DDJ May 1987, by Do-While Jones.

Dimensional Units

For most applications, you must make computations based on physical formulas, such as computing circuit quantities based on Ohm's law. Any physical formula involves terms that have units, such as volts, ohms, and amps. There are only a certain number of valid ways to combine dimensioned quantities, it makes sense to multiply amps by ohms to get volts. However, it usually doesn't make sense to multiply volts by ohms.

If you make careful use of Ada language features, the compiler can help you by detecting and rejecting incorrect combinations of units. If you are just writing a small program, this is probably not important --you can avoid them or easily find them on your own. But Ada was designed for writing large programs and, when many programmers are writing code that must mesh, they find the compiler is essential in detecting as many kinds of errors as possible.

Obviously, dimensional units are not what we normally think of as physical objects: they are characteristics of physical objects. A characteristic is an abstraction. But you can think of abstractions as existing as if they were themselves objects, having their own properties. I call abstractions like this "mental objects" because they become objects as you think of them. Thus, dimensional units are examples of mental objects.

The way to apply OOD to dimensional units is to think of each dimensional unit that your application area requires as being a class of objects. Thus, you might have a volt class or an amp class. Or, you might have a mile class, an hour class, and a miles-per-hour class. You would implement each such class as an Ada package that exports a type and related operations.

An Ada package is a means of grouping related programming entities. A package may contain, among other things, data types, variables, constants, procedures, and functions. A package may contain any number of procedures or functions that use the same name, as long as they have different argument lists. You can make these resources available to any other Ada program unit by simply naming the package in a context, or "with" clause as shown here:

with my__package;

A package consists of two parts -- a specification and a body. The specification describes the interface to the package's resources and tells what the package does. The body contains the actual Ada code and shows how the package resources do what they are required to do.

The form of the package construct is shown in Example 1, this page. This construct is part of a package specification for our prototype dimension class. The package's name is floatunit. It represents a class called float unit. The collection of resources in the package includes one type, called class, and a limited number of functions that can operate on objects of that type. Listing One on page 94, provides the entire package, including its specification and body. Ada uses extended or "dot" notation to refer to the resources in a package. Therefore, float__unit.class refers to the data type called class defined in the float unit package. Also, in the Listing two hyphens (- -) introduce a comment, which the compiler ignores.

Example 1: Using the Ada package construct to represent a class.

     package float_unit is
          type class is new float;

          function"*"(left : float;
                      right: class
                    ) return class;

          function "/" (left : class;
                      right: float
                    ) return class;

          -- etc. ...
     end float_unit;


In the package specification of Listing One, notice that the multiplication function (asterisk), takes a dimensionless object (left) of floating-point type, multiplies it by an object of our class (right), and produces a result that is of our class. This process should happen when, for example, you multiply the dimensionless number two times the distance to a neighboring town -- say, 30 miles -- to obtain the round trip distance of 60 miles.

Listing One demonstrates that float__unit inherits some valid operators from its parent type, float. These operators include addition, subtraction, and assignment. In addition float__unit defines some new multiplication and division operators, such as the multiplication function just described. It also overrides some inherited operators because they are invalid in the context of dimensional units. An example of an invalid inherited operator is the multiplication of two objects of the same class together to obtain a result of the same class:

function "*" (left, right:class) return class;

This operator is inherited from float where it is natural to multiply two floating-point (dimensionless) numbers to obtain another floating-point number. But for the dimentioned numbers the operator is invalid because if class is, say, foot and you multiply 5 feet by 4 feet, you do not get 20 feet -- you get 20 square feet. My method of overriding the invalid operator is to define the function in the specification of the package so that it overloads the inherited operator. Then in the body, I simply raise an exception if that particular function is called. This means that the error will not be detected until runtime, but at least it will be detected. Ada provides no way to detect it at compile time.

Listing One also contains two additional functions, image and value. The image function converts an object that is of float__unit.class or that is derived from float__unit.class into a string. The value function converts a string that has the proper syntax into an object of the class. The image and value functions are the inverse of each other. They provide a way to go between text and float__unit.class. They also serve to decouple the class packages from input-output classes. (We will discuss object coupling later in this article.) All classes derived from float__unit inherit image and value.

In this article, I am not going to make any objects of the float__unit.class just described. Instead, I will use float__unit.class as the parent for each units class like this:

with float__unit;
package hour is
   type class is new float__unit.class;
end hour;

These four lines of code create a new class package, hour, that is a subclass of the float unit class package. As such, hour inherits all the operations that were defined in the original package. A happy feature of this approach is that you do not need to write a package body for hour because Ada attributes the body of float-unit to it. You could create and use the objects of hour as shown in Example 2, page 53.

Example 2: Creating and using objects of the hour class

     with hour:    use hour;
     procedure time_card is
          -- Create the objects:
          hours_worked : hour.class;
          job_1: hour.class;
          job_2: hour.class;

     begin
          -- Give them each a value;
          job_1 := 8.0;
          job_2 := 5.5;
          hours_worked := job_1 + job_2;

     end time_card;


The with clause makes the hour class package visible to the time__card procedure. The use clause allows you to use the addition operation without having to use functional notation. Without use, you would have to write

hours__worked := hour." + "(job__1, job__2);

which is not as easy to read. The hours__worked declaration and the two lines following it create objects (variables) of that class. Each assignment statement (:=) gives an object a value.

So far, we have created one new package, hour, that is built up from the prototype dimensional unit package, float unit. To create other dimensional units, you could repeat the four lines of code that you used for hour, and just change the package name to the name of the desired unit. But you can take advantage of that similarity to reduce the number of lines of code from four to two, by using an Ada feature called generic. A generic program unit in Ada is like a template for making similar program units. Oversimplifying a bit, you might think of generic as a macro capability. Here, then is the template:

with float__unit;
generic
package unit is
     type class is newfloat__unit.class;
end unit;

Now, you can create new units packages like this:

with unit;
package hour is new unit;

This new package called hour is identical to the one created earlier that used four lines of code. The new hour inherits all the operations of float__unit. The process of creating an actual package from a generic template is called instantiation.

Fundamental and Composite Units

In any application area, there will be "fundamental" units and "composite" units. For example, mile and hour might be fundamental units while miles per hour is a composite unit, made up of the other two. You can create all the fundamental units as classes that directly inherit all the valid operations of the float__unit package. You can then create the composite units as classes with added or overridden operations. If you had an application dealing with velocity, you could create a mile class, reuse the hour class just described, and then create the mile__per__hour class as shown in Example 3 on page 54.

In Example 3, the hour class and the mile class inherit all the operations defined in the float__unit class. The mile__per__hour class inherits all those operations, but adds a new division operation. The new division operator allows you to divide a mile object by an hour object to obtain a mile__per__hour object. The mile__per__hour package requires its own body so that the details of the new division operation can be coded.

Example 3: Using the hour class and a new mile class to create the mile_per_hour class

     with float_unit;
     package mile is new unit;

     with float_unit;
     with hour;
     with mile;

     package mile_per_hour is
          type class is new float_unit.class;

          function "/" (left : mile.class;
                         right: hour.class
                        ) return class;

     end mile_per_hour;

You can reduce the effort of writing composite unit packages by again using Ada's generic facility. The generic facility helps you create one package for composite units made by dividing two other units (such as miles/hour), and another package for units made by multiplying two other units (such as square feet or foot-pounds). The complete specifications and bodies for both packages are shown in Listings Two and Three, starting on page 94. You customize these for your particular case, as shown in Example 4 on page 54.

Example 4: Installing the specification and the body parts for the packages listed in Examples 2 and 3

     with hour;
     with mile;
     with quotient_unit;

     package mile_per_hour is new quotient_unit (
          numerator_class => mile.class
          denominator_class => hour.class);

As you look at the generic packages in Listings Two and Three, notice that quotient__unit has a division operation as you would expect, but it also has two multiplication operations. Similarly, product__unit has two multiplication operations, but it also has two division operators. In both cases, the "extra" operations are ones that you would normally expect to perform on any composite unit. For example, the mile__per__hour class package created from quotient__unit provides the division operation so that you can divide mile objects to hour objects to mile__per__hour objects. But it also provides the multiplication operations so that you can multiply mile__per__hour by hour objects to obtain mile objects.

What about composite units that are square or cubic units? You would simply apply the product unit generic package as many times as required; for a package for cubic feet, see Example 5, on page 54.

Example 5: Creating new composite units by applying an existing generic package as many times as necessary. In this case, a package for cubic feet is built in three steps OOD UNITS

     with unit;
     package foot is new unit;

     with foot;
     with product_unit;
     package square_foot is a new product(
          class_a => foot,
          class_b => foot);

     with foot;
     with square_foot;
     with product_unit;
     package cubic_foot is new product_unit(
          class_a => foot,
          class_b => square_foot);

Conversions Between Units

Perhaps as you were reading, you wondered what to do about units that are simply scaled versions of each other. For example, how should you convert mile__per__hour to mile__per__second? Should each be a separate class? Should you write a function to convert one to the other? My answer is that they need to be separate classes because they are not directly combinable: you cannot add miles per hour and miles per second. But in simple cases, a special conversion function is not necessary. You could just multiply or divide by the proper constant to perform the conversion. For the case just described, the conversion factor is 1 hour/3600 seconds. Here is the code you might write:

with hour.class;
with second.class;
with mile__per__hour; use
       mile__per__hour;
with mile__per__second; use
    mile__per__second;
 ...
mph : mile__per__hour.class : = 60.0;
mps : mile__per__second.class;
...
mps : = mph 8 hour.class(1.0)/
   second.class(3600.0);
...

(The notation hour.class (1.0) is Ada's way of expressing the conversion of a floating-point number to the hour.class type.) You have to be careful, though. The mile__per__hour package knows how to perform the multiplication by hour objects to produce mile objects, so that operation must occur first. Then the mile__per__second package knows how to divide mile objects by second objects, so that operation needs to occur next. Imagine that you put the operation in reverse order like this:

mps := mph / second.class(3600.0) * hour.class(1.0);

The compiler would reject this line because the mile__per__hour class does not know anything about seconds.

Object Coupling

In general, it is probably safer and easier to create a function to perform the conversion from one unit to another. But where should you put that function? In the mile/second example, should you include the conversion function in the mile__per__hour package or the mile__per__second package? Or in both? Or neither? If neither, then where would the function go? If you put the function in the mile#erhour package, then that package is no longer self-contained and independent. The package then depends on, or is linked to, the mile__per__second package. You can see this if we try to put a conversion routine into that package, as shown in Example 6, page 56.

Example 6: Converting routines to couple a package with other packages

     with float_unit;
     with hour;
     with mile;
     with mile_per_second;

     package mile_per_hour is
          type class is new floar_unit.class;

          function "/" (left : mile.class;
                         right : hour.class
                       ) return class;

          function convert (mps:
                            mile_per_second.class
                           ) return class;
     end mile_per_hour;

Notice that Example 6 does not use the generic unit package. This is so you can add the special divide and convert functions. More importantly, notice that the convert routine operates on a class of objects (mile__per__second.class) not defined in the same package. When this happens, the two classes or objects are said to be coupled.

You might ask why it is bad to couple mile__per__hour to mile__per__second, but not bad to couple mile__per__hour to hour, mile, and float__unit. The reason is that it is impossible to have mile__per__hour without mile, hour, and float__unit, but it is possible to have mile__per__hour without mile__per__second. If two classes or objects are coupled, a change in one will usually affect the other. This propagation of changes can complicate software maintenance. So you should strive to minimize coupling between object and classes. The minimum coupling is that which exists in the domain you are modeling. If coupling exists in the real (physical or mental) world, your software should have similar coupling, otherwise it would not be an accurate model of this application area. In general then, only introduce coupling that is warranted by the nature of the objects and classes you are modeling.

So, how can you minimize the coupling between these two unit classes? Remember that in object-oriented design, classes and objects are created based on physical or mental classes and objects, and that dimensional units are mental objects -- namely, characteristics. What you have now is another mental object, called a relationship --specifically, the fact that one dimensional unit is related to another dimensional unit by some constant factor. This relationship is shown as an object in Example 7, page 56. (Only the package specification is shown in the example, you would still have to code its body.)

<a name="019d_000e"><a name="019d_000e">

Example 7

     with mile_per_hour;
     with mile_per_second;
     package mph_mps_convert is

               function relation(mph : mile_per_hour.class)
                                   return mile_per_second.class;

               function relation(mps : mile_per_second.class)
                                   return mile_per_hour.class;

     end mph_mps_convert;

Here, an object called mph__mps__convert has been created that models the relationship between mile__per__hour and mile__per__second. This relationship works both ways. You can convert mile__per__hour to mile__per__second and vice versa. Thus, there are two functions, one for each direction. Notice that even though both functions have the same name (relation), the Ada compiler chooses the correct one based on the type of the function's argument when the function is invoked. This new object is coupled to the two packages that it ties together, but it leaves the packages independent of each other. If for some reason you need to make a change in the mile__per__hour package, it might affect mph__mps__convert but would not affect the mile__per__second package.

The package mph__mps__convert is not a class because it cannot create any subclasses or objects (it does not export a type and it is not generic). But it is a full-fledged object because it has publicly accessible procedures that operate on otherwise inaccessible data, the conversion constant. You also can call it an object because it is a model of a mental object.

The naming convention followed here is to construct the name of the package by concatenating the names of the two related classes with the name of the relationship. The package name then identifies the two objects and the specific relationship. The reason for following this convention is that any two classes may be related to each other in many ways and each relationship should have its own package with a unique name. The name "relation" chosen for the functions in the package is used simply to indicate that this is a relationship object.

You can generalize this relationship object for dimensional units into a class as Example 8 illustrates on page 59. The class is implemented as a generic package that imports the two objects that are to be related and the conversion factor that relates them. A generic package like this represents a class because it is used to create packages that represent objects.

Example 8: Generalizing relationship objects for dimensional unit applications by creating a class that provides functions to go both ways. This is implemented as a generic package that imports the conversion factor and the two objects that are to be related

     generic
          -- Import one kind of class:
          type class_a is digits <>;
          --Import the other kind:
          type class_b is digits <>;
          -- Import the conversion factor
          a_to_b_factor : in float := 1.0;
     package class_a_class_b_convert is

          function relation (a : class_a
                            ) return class_b;

          function relation (b : class_b
                            ) return class_a;

     end class_a_class_b_convert;

     package body class_a_class_b_convert is

          function relation (a : class_a
                            ) return class_b
          is
          begin
                    return class_b(flaor (a) * a_to_b_factor);
          end relation;

          function relation (b : class_b
                            ) return class_a;

          is
          begin
                    return class_a(float(b) / a_to_b_factor);
          end relation;

     end class_a_class_b_convert;

Once the class__a__class___b__convert package of Example 8 exists, you can use it as shown in Example 9, next page, to make conversion objects for any two units classes that are related by a constant.

Example 9: Using the relationship class described in Example 8

        with mile_per_hour;
   with mile_per_second;
   with class_a_class_b_convert;
   package mph_mps_convert is new class_a_classz_b_convert (
      class_a => mile_per_hour.class,
      class_b => mile_per_second.class,
      a_to_b_factor => 3600.0);


Example 10, next page, shows a sample code fragrrnent that uses the mph__mps convert object package from Example 9 to convert from miles-per-hour to miles-per-second. You can see that the conversion amounts to no more than a subroutine call, but by careful design we have avoided unwanted coupling and solved the problem of "where to put the conversion routine."

Example 10: Code fragment showing the use of the mph__mps.convert objects created in Example 9 to convert 60 mile__per__hour into mile__per__second

     with mile_per_hour;              use mile_per_hour;
     with mile_per_second;            use mile_per_second;
     with mps_mph_convert;
     ...
     mph : mile_per_hour.class := 60.0;
     mps : mile_per_second.class;
     ...
     mps := mph_mps_convert.relation(mph);

Conclusion

The advantages of using the method described here to implement dimensional units are that it enlists the language's type checking abilities to keep you from incorrectly using dimensional units, it makes the creation of new unit classes easy, it handles "composite" units in a consistent way, it uses relationship objects to avoid object coupling, and it makes all the units and relationships reusable.

The concepts and techniques presented in this article are useful in many application areas -- not just dimensional units. I have focused on dimensional units here because they are abstract, and because newcomers to the OOD field have difficulty with abstractions when they try to apply object-oriented design to practical problems.

Acknowledgments

Thanks to my coworkers at Motorola, Randy Facklam, Mike Purcell, and Carl McNealy for their questions, ideas, and suggestions on the con-cepts discussed in this article.

Notes

Do-While Jones "Dimensional Data Types." Dr. Do bb `s Journal, May 1987, p. 50.

_OBJECT-ORIENTED DIMENSIONAL UNITS_

by John A. Grosbery

[LISTING ONE]

<a name="019d_0016">

package float_unit is
type class is new float;
units_error : exception;

function "*" (left,right : class) return class;
   -- This function is to overload the inherited
   -- multiply function. Multiplying two dimensioned
   -- numbers does not produce a number with the same
   -- units, so this is an invalid operation.  It will
   -- raise the units_error exception.

-- The following multiplication functions provide for
-- multiplying a non-dimensional number (float or
-- integer) times a dimensional number (class). There
-- are two of each (one with float first, one with
-- class first) to make the multiplication functions
-- commutative.

function "*" (left : float; right : class) return class;
function "*" (left : class; right : float) return class;

function "*" (left : integer; right : class)   return
class;
function "*" (left : class;   right : integer) return
class;

function "/" (left,right : class) return class;
   -- This function is to overload the inherited
   -- divide function. Dividing two dimensioned numbers
   -- does not produce a number with the same units, so
   -- this is an invalid operation.  It will raise the
   -- units_error exception.

function "/" (left, right : class) return float;
   -- This function divides two items of type class and
   -- returns the result as type float.  Dividing a
   -- dimensioned number by another of the same
   -- dimensioned produces a non-dimensional number.

-- The next two divide functions allow dividing a
-- dimensioned number by a non-dimensioned floating point
-- or integer number.  Doing so produces a result with
-- the same dimensions as the dimensioned number.

function "/" (left : class; right : float) return class;
function "/" (left : class; right : integer) return
class;

function "**" (left:class; right:integer) return class;
   -- This function is to overload the inherited
   -- exponentiation function. Exponentiating
   -- dimensioned numbers does not produce a
   -- number with the same units, so this is an
   -- invalid operation.  It will raise the
   -- units_error exception.

function image ( the_object :in class ) return string;
   -- This function will take the_object of type
   -- class and convert it to a string type.  The
   -- name "image" was chosen because the purpose of
   -- this function is similar to that of Ada's "image"
        -- attribute.  This function and the following
   -- decouple the units package from any input/output
   -- device or package.


function value (the_string :in string) return class;
   -- This function will take a string which is a valid
   -- representation of an object of the type class and
   -- convert it to the type class.  If the_string
   -- contains an invalid value, the constraint_error
   -- exception will be raised.  The name "value" was
   -- used because the purpose of this function is
   -- similar to Ada's "value" attribute.

end float_unit;

with text_io;

package body float_unit is
------------------------------------------------------------
function "*" (left,right : class) return class is
   -- This function is to hide the inherited multiply
   -- function. Multiplying two dimensioned numbers does
   -- not produce a number with the same units, so
   -- this is an invalid operation.  If this function
   -- is invoked, it will raise the units_error exception.

begin
   -- Whole function invalid; force exception:

   raise units_error;
   return left * right;

   -- Above return needed to satisfy compiler, but
   -- it will never be executed.
end "*";

function "*" (left : float; right : class) return class
is
begin
   return class(left * float(right));
end "*";


function "*" (left : class; right : float) return class
is
begin
    return class( float(left) * right );
end "*";

function "*" (left : integer; right : class)   return
class
is
begin
    return class( float(left) * right );
end "*";

function "*" (left : class;   right : integer) return
class
is
begin
    return class( left * float(right) );
end "*";

function "/" (left,right : class) return class
is
begin
   -- Whole function invalid; force exception:

    raise units_error;
    return class( float(left) / float(right));

   -- Above return needed to satisfy compiler, but
   -- it will never be executed.
end "/";

function "/" (left, right : class) return float
is
begin
    return float(left) / float(right);
end "/";

function "/" (left : class; right : float) return class
is
begin
    return class( float(left) / right);
end "/";

function "/" (left : class; right : integer) return class
is
begin
    return class( float(left) / float(right) );
end "/";

function "**" (left:class; right:integer) return class
is
begin
    raise units_error;
    return class( float(left) ** right);
end "**";

package fio is new text_io.float_io(class);
-- Fio will be needed by image and value, below.

function image ( the_object :in class ) return string
is
    buffer : string(1..14);
begin
    fio.put(buffer, the_object);
    return buffer;
end image;

function value (the_string :in string) return class
is
    buffer : class;
    last   : positive;
begin
    fio.get(the_string, buffer, last);
    return buffer;
end value;

end float_unit;


<a name="019d_0018">
<a name="019d_0017"><a name="019d_0017">
<a name="019d_0018">
[LISTING TWO]

------------------------------------------------------------
with float_unit;

generic
   type class_a is digits<>;
   type class_b is digits <>;
package product_unit is

type class is new float_unit.class;

function "*"(left   : class_a;
         right   : class_b) return class;

function "*"(left   : class_b;
         right   : class_a) return class;

function "/"(left   : class;
         right   : class_a) return class_b;

function "/"(left   : class;
         right   : class_b) return class_a;

end product_unit;

package body product_unit is

function "*"(left   : class_a;
         right   : class_b) return class
is
begin
   return class(float(left) * float(right));
end "*";

function "*"(left   : class_b;
         right   : class_a) return class
is
begin
   return class(float(left) * float(right));
end "*";

function "/"(left   : class;
         right   : class_a) return class_b
is
begin
   return class_b(float(left) / float(right));
end "/";

function "/"(left   : class;
         right   : class_b) return class_a
is
begin
   return class_a(float(left) / float(right));
end "/";

end product_unit;




<a name="019d_0019"><a name="019d_0019">
<a name="019d_001a">
[LISTING THREE]
<a name="019d_001a">
------------------------------------------------------------

with float_unit;

generic
   type numerator_class is digits <>;
   type denominator_class is digits <>;
package quotient_unit is

type class is new float_unit.class;

function "/"(left   : numerator_class;
              right   : denominator_class
) return class;

function "*"(left   : class;
         right   : denominator_class
) return numerator_class;

function "*"(left   : denominator_class;
         right   : class
) return numerator_class;

end quotient_unit;

package body quotient_unit is

function "/"(left   : numerator_class;
         right   : denominator_class) return class
is
begin
   return class(float(left) / float(right));
end "/";

function "*"(left   : class;
         right   : denominator_class
) return numerator_class
is
begin
   return numerator_class(float(left) * float(right));
end "*";

function "*"(left   : denominator_class;
         right   : class
) return numerator_class
is
begin
   return numerator_class(float(left) * float(right));
end "*";

end quotient_unit;


Example 1: Using the form for a package construct


      package float_unit  is
         type class is new float;

         function"*"(left : float;
               right: class
            ) return  class;

         function "/" (left: class;
               right: float
            ) return class;

         -- etc...
      end float_unit;



Example 2: Creating objects of the hour glass

      with hour;  use hour;
      procedure time_card is
         -- Create the objects:
         hours_worked : hour.class;
         job_1 : hour.class;
         job_2 : hour.class;

      begin
         -- Give them each a value:
         job_1 := 8.0;
         job_2 := 5.5;
         hours_worked := job_1 + job_2;

      end time_card;



Example 3: Using the hour class and a new mile class to create
the mile_per_hour class

      with float_unit;
      package mile is new unit;

      with float_unit;
      with hour;
      with mile;

      package mile_per_hour is
         type class is new float_unit.class;

         function "/"(left : mile.class;
                      right: hour.class
                  ) return class;

      end mile_per_hour;


>Example 4: Installing the specification and the body for the
packages listed in Example 2 and 3

      with hour;
      with mile;
      with quotient_unit;

      package mile_per_hour is new quotient_unit(
         numerator_class => mile.class,
         denominator_class => hour.class);



Example 5: Creating new composite units by applying an existing
generic package as many times as necessary. In this case, a
package for cubic feet is created from miles/hour.

      with unit;
      package foot is new unit;

      with foot;
      with product_unit;
      package square_foot is new product_unit(
         class_a => foot,
         class_b => foot);


      with foot;
      with square_foot;
      with product_unit;
      package cubic_foot is new product_unit(
         class_a => foot,
         class_b => square_foot);



Example 6: Converting routines to couple a package with other
packages

      with float_unit;
      with hour;
      with mile;
      with mile_per_second;

      package mile_per_hour is
         type class is new float_unit.class;

         function "/"(left  : mile.class;
                        right : hour.class
               ) return class;


         function convert (mps :
               miles_per_second.class
               ) return class;
      end mile_per_hour;



Example 7: Modelling relationships on objects


      with mile_per_hour;
      with mile_per_second;
      package mph_mps_convert  is

         function relation(mph :
                  mile_per_hour.class)
            return mile_per_second.class;

         function relation(mps :
                  mile_per_second.class)
            return mile_per_hour.class;
      end mph_mps_convert;


Example 8: Generalizing relationship objects for dimensional unit
applications by creating a class that provides functions to go
both ways. This generalization process is implemented as a
generic package that imports the conversion factor and the two
objects that are to be related.

      generic
         -- Import one kind of class:
         type class_a is digits <>;
         -- Import the other kind:
         type class_b is digits <>;
                         -- Import the conversion factor
         a_to_b_factor : in float := 1.0;
      package class_a_class_b_convert is

         function relation (a : class_a
               ) return class_b;


         function relation (b : class_b
               ) return class_a;

      end class_a_class_b_convert;


      package body class_a_class_b_convert is

         function relation (a : class_a
               ) return class_b
         is
         begin
            return class_b(float(a) *
                     a_to_b_factor);
         end relation;


         function relation (b : class_b
               ) return class_a;
         is
         begin
            return class_a(float(b) /
                   a_to_b_factor);
         end relation;

      end class_a_class_b_convert;


Example 9: Using the relationship objects described in Example 8


      with miles_per_hour;
      with miles_per_second;
      with class_a_class_b_convert;
      package mph_mps_convert is
            new class_a_class_b_convert(
         class_a => miles_per_hour.class,
         class_b => miles_per_second.class,
         a_to_b_factor => 3600.0);

Example 10: Code fragment showing the use of the mph_mps.convert
object created in Example 9 to convert 60 mile_per_hour into
mile_per_second

      with miles_per_hour;   use miles_per_hour;
      with miles_per_second;   use miles_per_second;
      with mph_mps_convert;
      ...
      mph : miles_per_hour.class := 60.0;
      mps  : miles_per_second.class;
      ...
      mps  := mph_mps_convert.relation(mph);
      ...









Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.