Ada 95 Quality and Style Guide Chapter 8
8.3.5 Using Generic Units for Data Abstraction
guideline
rationale
guideline
rationale
guideline
rationale
guideline
rationale
A reusable part should be as independent as possible from other
reusable parts. A potential user is less inclined to reuse a part
if that part requires the use of other parts that seem unnecessary.
The "extra baggage" of the other parts wastes time and
space. A user would like to be able to reuse only that part that
is perceived as useful.
The concept of a "part" is intentionally vague here.
A single package does not need to be independent of each other
package in a reuse library if the "parts" from that
library that are typically reused are entire subsystems. If the
entire subsystem is perceived as providing a useful function,
the entire subsystem is reused. However, the subsystem should
not be tightly coupled to all the other subsystems in the reuse
library so that it is difficult or impossible to reuse the subsystem
without reusing the entire library. Coupling between reusable
parts should only occur when it provides a strong benefit perceptible
to the user.
8.4.1 Subsystem Design
guideline
See also Guidelines 4.1.6 and 8.3.1.
guideline
can be rewritten as a generic unit:
and then instantiated:
rationale
The first (nongeneric) version of Produce_And_Store_A
above is difficult to reuse because it depends on Package_A
that may not be general purpose or generally available. If the
operation Produce_And_Store has reuse potential that
is reduced by this dependency, a generic unit and an instantiation
should be produced as shown above. The with clause for
Package_A has been moved from the Produce_And_Store
generic procedure, which encapsulates the reusable algorithm to
the Produce_And_Store_A instantiation. Instead of naming
the package that provides the required operations, the generic
unit simply lists the required operations themselves. This increases
the independence and reusability of the generic unit.
This use of generic formal parameters in place of with
clauses also allows visibility at a finer granularity. The with
clause on the nongeneric version of Produce_And_Store_A
makes all of the contents of Package_A visible to Produce_And_Store_A,
while the generic parameters on the generic version make only
the Produce and Store operations available to
the generic instantiation.
Generic formal packages allow for
"safer and simpler composition of generic abstractions"
(Rationale 1995, §12.6). The generic formal package allows
you to group a set of related types and their operations into
a single unit, avoiding having to list each type and operation
as an individual generic formal parameter. This technique allows
you to show clearly that you are extending the functionality of
one generic with another generic, effectively parameterizing one
abstraction with another.
guideline
When there is clear requirement for a recursive dependency, you
should use pragma Elaborate_Body. This situation arises,
for example, when you have a recursive dependency (i.e., package
A's body depends on package B's specification and package B's
body depends on package A's specification).
Any time you call a subprogram (typically a function) during the
elaboration of a library unit, the body of the subprogram must
have been elaborated before the library unit. You can ensure this
elaboration happens by adding a pragma Elaborate_Body
for the unit containing the function. If, however, that function
calls other functions, then it is safer to put a pragma Elaborate_All
on the unit containing the function.
For a discussion of the pragmas Pure and
Preelaborate, see also the Ada
Reference Manual (1995, §10.2.1) and the Rationale (1995,
§10.3). If you use either pragma Pure or Preelaborate,
you will not need the pragma Elaborate_Body.
The idea of a registry is fundamental to many object-oriented
programming frameworks. Because other library units will need
to call it during their elaboration, you need to make sure that
the registry itself is elaborated early. Note that the registry
should only depend on the root types of the type hierarchies and
that the registry should only hold "class-wide" pointers
to the objects, not more specific pointers. The root types should
not themselves depend on the registry. See Chapter 9 for a more
complete discussion of the use of object-oriented features.
guideline
Guideline 9.2.4 discusses the use of tagged types in building
different versions of similar parts (i.e., common interface, multiple
implementations).
guideline
rationale
This feature should be used when other factors prevent the code
from being separated into separate program units. In the above
example, it would be preferable to have a different procedure
for each algorithm. But the algorithms may differ in slight but
complex ways to make separate procedures difficult to maintain.
guideline
See Guideline 5.3.4 for a short discussion of using access-to-subprogram
types in implementing table-driven programs.
Table-driven programs are often more efficient and easier to read
than the corresponding case or
guideline
Assuming a variable S of type String, the following
expression:
can now be replaced by more portable and preexisting language-defined
operations such as:
rationale
guideline
Given the following array declaration:
you can write a procedure that computes the wage of each employee,
regardless of the different types of employees that you create.
The Employee_List consists of an array of pointers to
the various kinds of employees, each of which has an individual
Compute_Wage procedure. (The primitive Compute_Wage
is declared as an abstract procedure and, therefore, must be overridden
by all descendants.) You will not need to modify the payroll code
as you specialize the kinds of employees:
rationale
A general root tagged type can define the common properties and
have common operations for a hierarchy of more specific types.
Software that depends only on this root type will be general,
in that it can be used with objects of any of the more specific
types. Further, the general algorithms of clients of the root
type do not have to be changed as more specific types are added
to the type hierarchy. This is a particularly effective way to
organize object-oriented software for reuse.
Separating the hierarchy of derived tagged types into individual
packages enhances reusability by reducing the number of items
in package interfaces. It also allows you to with only
the capabilities needed.
See also Guidelines 9.2, 9.3.1, 9.3.5, and 9.4.1.
understanding and clarity
example
example
example
example
8.4 INDEPENDENCE
rationale
example
------------------------------------------------------------------------
with Package_A;
procedure Produce_And_Store_A is
...
begin -- Produce_And_Store_A
...
Package_A.Produce (...);
...
Package_A.Store (...);
...
end Produce_And_Store_A;
------------------------------------------------------------------------
------------------------------------------------------------------------
generic
with procedure Produce (...);
with procedure Store (...);
procedure Produce_And_Store;
------------------------------------------------------------------------
procedure Produce_And_Store is
...
begin -- Produce_And_Store
...
Produce (...);
...
Store (...);
...
end Produce_And_Store;
------------------------------------------------------------------------
------------------------------------------------------------------------
with Package_A;
with Produce_And_Store;
procedure Produce_And_Store_A is
new Produce_And_Store (Produce => Package_A.Produce,
Store => Package_A.Store);
------------------------------------------------------------------------
example
---------------------------------------------------------------------------
generic
...
package Stack is
pragma Elaborate_Body (Stack); -- in case the body is not yet elaborated
...
end Stack;
---------------------------------------------------------------------------
with Stack;
package My_Stack is
new Stack (...);
---------------------------------------------------------------------------
package body Stack is
begin
...
end Stack;
---------------------------------------------------------------------------
example
example
------------------------------------------------------------------------
package Matrix_Math is
...
type Algorithm is (Gaussian, Pivoting, Choleski, Tri_Diagonal);
generic
Which_Algorithm : in Algorithm := Gaussian;
procedure Invert ( ... );
end Matrix_Math;
------------------------------------------------------------------------
package body Matrix_Math is
...
---------------------------------------------------------------------
procedure Invert ( ... ) is
...
begin -- Invert
case Which_Algorithm is
when Gaussian => ... ;
when Pivoting => ... ;
when Choleski => ... ;
when Tri_Diagonal => ... ;
end case;
end Invert;
---------------------------------------------------------------------
end Matrix_Math;
------------------------------------------------------------------------
example
if-elsif-else networks to compute the item being sought
or looked up.
example
function Upper_Case (S : String) return String is
subtype Lower_Case_Range is Character range 'a'..'z';
Temp : String := S;
Offset : constant := Character'Pos('A') - Character'Pos('a');
begin
for Index in Temp'Range loop
if Temp(Index) in Lower_Case_Range then
Temp(Index) := Character'Val (Character'Pos(Temp(Index)) + Offset);
end if;
end loop;
return Temp;
end Upper_Case;
with Ada.Characters.Latin_1;
function Trim (S : String) return String is
Left_Index : Positive := S'First;
Right_Index : Positive := S'Last;
Space : constant Character := Ada.Characters.Latin_1.Space;
begin
while (Left_Index < S'Last) and then (S(Left_Index) = Space) loop
Left_Index := Positive'Succ(Left_Index);
end loop;
while (Right_Index > S'First) and then (S(Right_Index) = Space) loop
Right_Index := Positive'Pred(Right_Index);
end loop;
return S(Left_Index..Right_Index);
end Trim;
Upper_Case(Trim(S))
with Ada.Characters.Handling; use Ada.Characters.Handling;
with Ada.Strings; use Ada.Strings;
with Ada.Strings.Fixed; use Ada.Strings.Fixed;
...
To_Upper (Trim (Source => S, Side => Both))
example
with Wage_Info;
package Personnel is
type Employee is abstract tagged limited private;
type Employee_Ptr is access all Employee'Class;
...
procedure Compute_Wage (E : Employee) is abstract;
private
type Employee is tagged limited record
Name : ...;
SSN : ... ;
Rates : Wage_Info.Tax_Info;
...
end record;
end Personnel;
package Personnel.Part_Time is
type Part_Timer is new Employee with private;
...
procedure Compute_Wage (E : Part_Timer);
private
...
end Personnel.Part_Time;
package Personnel.Full_Time is
type Full_Timer is new Employee with private;
...
procedure Compute_Wage (E : Full_Timer);
private
...
end Personnel.Full_Time;
type Employee_List is array (Positive range <>) of Personnel.Employee_Ptr;
procedure Compute_Payroll (Who : Employee_List) is
begin -- Compute_Payroll
for E in Who'Range loop
Compute_Wage (Who(E).all);
end loop;
end Compute_Payroll;
8.5 SUMMARY
robustness
adaptability
independence
In This Guide:
Table of Contents
Chapter 1
Chapter 2
Chapter 3
Chapter 4
Chapter 5
Chapter 6
Chapter 7
Chapter 8
Chapter 9
Chapter 10
Chapter 11
Appendix
References
Bibliography
Index