|Ada 95 Quality and Style Guide||Chapter 5|
5.3.1 Derived Types and Subtypes
Use existing types as building blocks by deriving new types from them.
Use range constraints on subtypes.
Define new types, especially derived types, to include the largest set of possible values, including boundary values.
Constrain the ranges of derived types with subtypes, excluding boundary values.
Use type derivation rather than type extension when there are no meaningful components to add to the type.
Type Table is a building block for the creation of new types:type Table is record Count : List_Size := Empty; List : Entry_List := Empty_List; end record; type Telephone_Directory is new Table; type Department_Inventory is new Table;
The following are distinct types that cannot be intermixed in operations that are not programmed explicitly to use them both:type Dollars is new Number; type Cents is new Number;
Below, Source_Tail has a value outside the range of Listing_Paper when the line is empty. All the indices can be mixed in expressions, as long as the results fall within the correct subtypes:type Columns is range First_Column - 1 .. Listing_Width + 1; subtype Listing_Paper is Columns range First_Column .. Listing_Width; subtype Dumb_Terminal is Columns range First_Column .. Dumb_Terminal_Width; type Line is array (Columns range <>) of Bytes; subtype Listing_Line is Line (Listing_Paper); subtype Terminal_Line is Line (Dumb_Terminal); Source_Tail : Columns := Columns'First; Source : Listing_Line; Destination : Terminal_Line; ... Destination(Destination'First .. Source_Tail - Destination'Last) := Source(Columns'Succ(Destination'Last) .. Source_Tail);
The name of a derived type can make clear its intended use and avoid proliferation of similar type definitions. Objects of two derived types, even though derived from the same type, cannot be mixed in operations unless such operations are supplied explicitly or one is converted to the other explicitly. This prohibition is an enforcement of strong typing.
Define new types, derived types, and subtypes cautiously and deliberately. The concepts of subtype and derived type are not equivalent, but they can be used to advantage in concert. A subtype limits the range of possible values for a type but does not define a new type.
Types can have highly constrained sets of values without eliminating useful values. Used in concert, derived types and subtypes can eliminate many flag variables and type conversions within executable statements. This renders the program more readable, enforces the abstraction, and allows the compiler to enforce strong typing constraints.
Many algorithms begin or end with values just outside the normal range. If boundary values are not compatible within subexpressions, algorithms can be needlessly complicated. The program can become cluttered with flag variables and special cases when it could just test for zero or some other sentinel value just outside normal range.
The type Columns and the subtype Listing_Paper in the example above demonstrate how to allow sentinel values. The subtype Listing_Paper could be used as the type for parameters of subprograms declared in the specification of a package. This would restrict the range of values that could be specified by the caller. Meanwhile, the type Columns could be used to store such values internally to the body of the package, allowing First_Column - 1 to be used as a sentinel value. This combination of types and subtypes allows compatibility between subtypes within subexpressions without type conversions as would happen with derived types.
The choice between type derivation and type extension depends on what kind of changes you expect to occur to objects in the type. In general, type derivation is a very simple form of inheritance: the derived types inherit the structure, operations, and values of the parent type (Rationale 1995, §4.2). Although you can add operations, you cannot augment the data structure. You can derive from either scalar or composite types.
Type extension is a more powerful form of inheritance, only applied to tagged records, in which you can augment both the type's components and operations. When the record implements an abstraction with the potential for reuse and/or extension, it is a good candidate for making it tagged. Similarly, if the abstraction is a member of a family of abstractions with well-defined variable and common properties, you should consider a tagged record.
The price of the reduction in the number of independent type declarations is that subtypes and derived types change when the base type is redefined. This trickle-down of changes is sometimes a blessing and sometimes a curse. However, usually it is intended and beneficial.
|< Previous Page||Search||Contents||Index||Next Page >|