|Ada 95 Quality and Style Guide||Chapter 9|
9.2.4 Abstract Types
Consider using abstract types and operations in creating classification schemes, for example, a taxonomy, in which only the leaf objects will be meaningful in the application.
Consider declaring root types and internal nodes in a type tree as abstract.
Consider using abstract types for generic formal derived types.
Consider using abstract types to develop different implementations of a single abstraction.
In a banking application, there are a wide variety of account types, each with different features and restrictions. Some of the variations are fees, overdraft protection, minimum balances, allowable account linkages (e.g., checking and savings), and rules on opening the account. Common to all bank accounts are ownership attributes: unique account number, owner name(s), and owner tax identification number(s). Common operations across all types of accounts are opening, depositing, withdrawing, providing current balance, and closing. The common attributes and operations describe the conceptual bank account. This idealized bank account can form the root of a generalization/specialization hierarchy that describes the bank's array of products. By using abstract tagged types, you ensure that only account objects corresponding to a specific product will be created. Because any abstract operations must be overridden with each derivation, you ensure that any restrictions for a specialized account are implemented (e.g., how and when the account-specific fee structure is applied):-------------------------------------------------------------------------- package Bank_Account_Package is type Bank_Account_Type is abstract tagged limited private; type Money is delta 0.01 digits 15; -- The following abstract operations must be overridden for -- each derivation, thus ensuring that any restrictions -- for specialized accounts will be implemented. procedure Open (Account : in out Bank_Account_Type) is abstract; procedure Close (Account : in out Bank_Account_Type) is abstract; procedure Deposit (Account : in out Bank_Account_Type; Amount : in Money) is abstract; procedure Withdraw (Account : in out Bank_Account_Type; Amount : in Money) is abstract; function Balance (Account : Bank_Account_Type) return Money is abstract; private type Account_Number_Type is ... type Account_Owner_Type is ... type Tax_ID_Number_Type is ... type Bank_Account_Type is abstract tagged limited record Account_Number : Account_Number_Type; Account_Owner : Account_Owner_Type; Tax_ID_Number : Tax_ID_Number_Type; end record; end Bank_Account_Package; -------------------------------------------------------------------------- -- Now, other specialized accounts such as a savings account can -- be derived from Bank_Account_Type as in the following example. -- Note that abstract types are still used to ensure that only -- account objects corresponding to specific products will be -- created.with Bank_Account_Package; with Bank_Account_Package; package Savings_Account_Package is type Savings_Account_Type is abstract new Bank_Account_Package.Bank_Account_Type with private; -- We must override the abstract operations provided -- by Bank_Account_Package. Since we are still declaring -- these operations to be abstract, they must also be -- overridden by the specializations of Savings_Account_Type. procedure Open (Account : in out Savings_Account_Type) is abstract; procedure Close (Account : in out Savings_Account_Type) is abstract; procedure Deposit (Account : in out Savings_Account_Type; Amount : in Bank_Account_Package.Money) is abstract; procedure Withdraw (Account : in out Savings_Account_Type; Amount : in Bank_Account_Package.Money) is abstract; function Balance (Account : Savings_Account_Type) return Bank_Account_Package.Money is abstract; private type Savings_Account_Type is abstract new Bank_Account_Package.Bank_Account_Type with record Minimum_Balance : Bank_Account_Package.Money; end record; end Savings_Account_Package; --------------------------------------------------------------------------
See the abstract set package in Guideline 9.5.1 for an example of creating an abstraction with a single interface and the potential for multiple implementations. The example only shows one possible implementation; however, you could provide an alternate implementation of the Hashed_Set abstraction using other data structures.
In many classification schemes, for example, a taxonomy, only objects at the leaves of the classification tree are meaningful in the application. In other words, the root of the hierarchy does not define a complete set of values and operations for use by the application. The use of "abstract" guarantees that there will be no objects of the root or intermediate nodes. Concrete derivations of the abstract types and subprograms are required so that the leaves of the tree become objects that a client can manipulate.
You can only declare abstract subprograms when the root type is also abstract. This is useful as you build an abstraction that forms the basis for a family of abstractions. By declaring the primitive subprograms to be abstract, you can write the "common class-wide parts of a system . . . without being dependent on the properties of any specific type at all" (Rationale 1995, §4.2).
Abstract types and operations can help you resolve problems when your tagged type hierarchy violates the expected semantics of the class-wide type dispatching operations. The Rationale (1995, §4.2) explains:
When building an abstraction that is to form the basis of a class of types, it is often convenient not to provide actual subprograms for the root type but just abstract subprograms which can be replaced when inherited. This is only allowed if the root type is declared as abstract; objects of an abstract type cannot exist. This technique enables common class-wide parts of a system to be written without being dependent on the properties of any specific type at all. Dispatching always works because it is known that there can never be any objects of the abstract type and so the abstract subprograms could never be called.
See Guidelines 8.3.8 and 9.2.1.
The multiple inheritance techniques discussed in Guideline 9.5.1 make use of abstract tagged types. The basic abstraction is defined using an abstract tagged (limited) private type (whose full type declaration is a null record) with a small set of abstract primitive operations. While abstract operations have no bodies and thus cannot be called, they are inherited. Derivatives of the abstraction then extend the root type with components that provide the data representation and override the abstract operations to provide callable implementations (Rationale 1995, §4.4.3). This technique allows you to build multiple implementations of a single abstraction. You declare a single interface and vary the specifics of the data representation and operation implementation.
When you use abstract data types as described in this guideline, you can have multiple implementations of the same abstraction available to you within a single program. This technique differs from the idea of writing multiple package bodies to provide different implementations of the abstraction defined in a package specification because with the package body technique, you can only include one of the implementations (i.e., bodies) in your program.
|< Previous Page||Search||Contents||Index||Next Page >|