This chapter introduces the package construct. A package is an elegant way of encapsulating code and data that interact together into a single unit. A package may be used in a variety of ways, this chapter however will promote it use to define classes.
Chapter Index |
The world in which we live is populated by many devices and machines that make everyday living easier and more enjoyable. The TV, for instance, is viewed by almost every person in the country, yet few understand exactly what happens inside the box. Likewise, there are many millions of motorists who drive regularly and do not need a detailed knowledge of the workings of a car to make effective use of it.
To many people, their knowledge of a car is as shown in Figure 5.1.
Figure 5.1 Basic understanding of working of an automatic car.
The details of what happens inside the car are not important for most day-to-day driving.
In essence the world is populated with many objects which have an interface that allows the humblest of persons to make effective use of the item. We sometimes criticise the interfaces as being ineffective and difficult to use, yet in most cases we would prefer to use the objects as they stand, rather than having to perform the task by other means.
Likewise in the software world, there are objects that a user or programmer can make effective use of without having to know how the object has been implemented. On a very simple level an Ada program may declare objects to hold floating point numbers, which can then be used with arithmetic operations to sum, multiply, etc. these values. Most programmers however, do not know the exact details of how these operations are performed; they accept the interface provided by the programming language.
At one point it was fashionable for programming languages to provide a rich set of data types. The designers of these languages hoped the data types provided would be adequate for all occasions. The problem was, and still is, that no one language could ever hope to provide all the different types of item that a programmer may need or wish to use.
Ada gives a programmer the ability to declare new data types, together with a range of operations that may be performed on an instance of the type. Naturally, a programmer may also use types and operations on these types that have been defined by other programmers.
Chapter Index |
A car can be thought of as an object. The car contains complex details and processes that are hidden from the driver. For example, to make the car go faster the driver presses the accelerator peddle. The car receives the message go faster and evokes an internal method to speed up the engine.
In the above description of driving a car many object-oriented ideas have been used. These ideas are as follows:
object | An item that has a hidden internal structure. The hidden structure is manipulated or accessed by messages sent by a user. |
message | A request sent to the object to obey one of its methods. |
method | A set of actions that manipulates or accesses the internal state of the object. The details of these actions are hidden from a user of the object. |
Chapter Index |
In Ada an object is an instance of either a user defined type or an instance of one of the in-built types.
An object for a user defined type can be imagined diagrammatically as Figure 5.2.
Figure 5.2 Diagrammatic representation of an object.
A message is implemented as either a procedure or function call. The body of which is the method that is evoked when the message is sent to the object. The user of the object has no knowledge of the implementation code contained in the body of the procedure of function.
.
Chapter Index |
Before looking in detail at the implementation of an object that represents a bank account, it is appropriate to consider the messages that might be sent to such an object. For a very simple type of bank account these messages would be:
The following program demonstrates the sending of these messages to an instance of an Account.
with Ada.Text_io, Class_account; use Ada.Text_io, Class_account; procedure main is my_account: Account; obtain : Money; begin statement( my_account ); put("Deposit £100.00 into account"); new_line; deposit( my_account, 100.00 ); statement( my_account ); put("Withdraw £80.00 from account"); new_line; withdraw( my_account, 80.00, obtain ); statement( my_account ); put("Deposit £200.00 into account"); new_line; deposit( my_account, 200.00 ); statement( my_account ); end main; |
The messages sent to an instance of an Account are: deposit, withdraw, balance, statement . For example, to deposit £100 into my_accountthe following procedural notation is used:
deposit( my_account, 100.00 ); |
This should be read as: send the message deposit to the object my_account with a parameter of 100.00.
To withdraw money from the account a programmer would send the message withdraw to the object my_account with two parameters, the amount to withdraw and a variable that is to be filled with the amount actually withdrawn. The implementation of the method will check that the person has sufficient funds in their account to allow the transaction to take place. This is written as:
withdraw( my_account, 80.00, obtain ); |
Chapter Index |
When compiled with an appropriate package body, the above program unit when run will produce the following results:
Mini statement: The amount on deposit is £ 0.00 Deposit £100.00 into account Mini statement: The amount on deposit is £100.00 Withdraw £80.00 from account Mini statement: The amount on deposit is £20.00 Deposit £200.00 into account Mini statement: The amount on deposit is £220.00 |
Chapter Index |
The package construct in Ada is split into two distinct parts. These parts contain the following object oriented components:
Ada Package component | Object oriented component |
Specification | The type used to elaborate the object, plus the specification of the message that can be sent to an instance of the type. |
Implementation | Implementation of the methods that are invoked when a message is sent to the object. |
Chapter Index |
The specification defines what the packages does, but not how it performs the implementation. It is used by the Ada compiler to check and enforce the correct usage of the package by a programmer.
The specification is split into two distinct parts: a public part and a private part. The public part defines the messages that may be sent to an instance of an Account, whilst the private part defines the representation of the type Account .
As the representation of Account is defined in the private part of the specification, a user of an instance of an Account will not be allowed to access the internal representation. The user however, is allowed to declare and, depending on the description of the type, assign and compare for equality and inequality. In this case the description of the type is private and a user is allowed to declare, assign and compare for equality and inequality instances of Account.
package Class_account is type Account is private; subtype Money is Float; subtype PMoney is Float range 0.0 .. Float'Last; procedure statement( the:in Account ); procedure deposit ( the:in out Account; amount:in PMoney ); procedure withdraw( the:in out Account; amount:in PMoney; get:out PMoney ); function balance ( the:in Account ) return Money; private type Account is record balance_of : Money := 0.00; -- Amount in account end record; end Class_account; |
The component parts of the specification are illustrated in Figure 5.3
Figure 5.3 Components of the specification part of a package.
The representation of Account (which is defined in the private part) is a subtype of a Float that will have an initial value of 0.00. An Ada record groups together several type declarations into a single named type. In this case the record type Account declares a single object called balance_of. Section 6.1 in chapter 6 describes the record type.
Chapter Index |
The package Class_account represents internally the balance of the account as a subtype of a Float. A Floatis an inexact way of representing numbers as only the most significant digits of the number will be stored. Instances of a type declaration of the form type Money is delta 0.01 digits 8; would provide a more reliable way of holding the balance of the account. However, this would require instantiation of a specific package for input and output of objects of this type. To simplify the presentation of this package the representation of the balance of the account is implemented as a subtype of a Float. After reading chapters 13 and 17 the reader may wish to re-implement this package as a generic package which uses an instantiation of Ada.Text_io.Decimal_io.
Chapter Index |
The implementation of the package Class_account is as follows:
with Simple_io; use Simple_io; package body Class_account is procedure statement( the:in Account ) is begin put("Mini statement: The amount on deposit is $" ); put( the.balance_of, aft=>2, exp=>0 ); new_line(2); end statement; procedure deposit ( the:in out Account; amount:in PMoney ) is begin the.balance_of := the.balance_of + amount; end deposit; procedure withdraw( the:in out Account; amount:in PMoney; get:out PMoney ) is begin if the.balance_of >= amount then the.balance_of := the.balance_of - amount; get := amount; else get := 0.00; end if; end withdraw; function balance( the:in Account ) return Money is begin return the.balance_of; end balance; end Class_account; |
The body of the package contains the definition of the procedures and functions defined in the specification part of the package. f Account the . notation is used. For example, in the function balance the result returned is obtained using the statement return the.balance_of; The . notation is used to access a component of an instance of a record type. In this case the instance of the record type is the object the and the component of the object is balance_of.
Chapter Index |
The following terminology is used to describe the components of a class.
Terminology |
Example: in class Account |
Explanation: |
Instance attribute | balance_of | A data component of an object. In Ada this will be a member of the type that is used to declare the object. |
Instance method or just method | deposit | A procedure or function used to access the instance attributes in an object. |
Note: The terminology comes from the language smalltalk.
Chapter Index |
A user will normally only have access to the specification part of a package. This provides a specification of the messages that can be sent to an object but does not show how the methods invoked by the messages have been implemented. The implementation part will not normally be available, the implementor normally providing only a compiled version of the package.
Unfortunately the details of the private type will normally be visible, though they can not be accessed.
Chapter Index |
When building a package the implementor should ensure:
The visibility hierarchy for the package Class_accountis shown in figure 5.4.
Figure 5.4 Visibility of methods and instance attributes of the package Class_account.
Chapter Index |
In object oriented programming one of the important ideas is that of the class. A class is the collective name for all objects that share the same structure and behaviour. For example, in a program dealing with bank transactions all the objects that represent a particular type of bank account would belong to the same class.
The class construct in a programming language is used to define objects that share a common structure and behaviour. Ada does not have a class construct.
However, Ada's package construct can be used to simulate the class construct found in other object-oriented programming languages. For example, a class Account is defined by the following package:
package Class_account is type Account is private; subtype Money is Float; subtype PMoney is Float range 0.0 .. Float'Last; procedure statement( the:in Account ); -- other instance methods private type Account is record balance_of : Money := 0.00; -- Amount in account end record; end Class_account; package body Class_account is -- Implementation of the procedures and functions end Class_account; |
In defining a class the following conventions are used:
Chapter Index |
The clauses with Ada.Text_io; use Ada.Text_io; make available the contents of the package Ada. Text_io to the following program unit. The package Ada.Text_io contains definitions for performing input and output on character and string objects. The exact effect of these clauses are as follows:
Thus without the use class the program to process bank transactions would become:
with Ada.Text_io, Class_account; procedure main ismy_account:Class_account.Account; obtain :Money; begin Class_account.statement( my_account ); Ada.Text_io.put("Deposit £100.00 into account"); Ada.Text_io.new_line; Class_account.deposit( my_account, 100.00 ); Class_account.statement( my_account ); Ada.Text_io.put("Withdraw £80.00 from account"); Ada.Text_io.new_line; Class_account.withdraw( my_account, 80.00, obtain ); Class_account.statement( my_account ); Ada.Text_io.put("Deposit £200.00 into account"); Ada.Text_io.new_line; Class_account.deposit( my_account, 200.00 ); Class_account.statement( my_account ); end main; |
Note: Some program guidelines will ban the use of a use clause.
Chapter Index |
Use a use clause | Do not use a use clause |
Program writing is simplified
Confusion may arise as to which package the item used is a component of. |
A program must explicitly state which package the component is taken from. This can reduce the possibility of program error due to accidental misuse. |
Chapter Index |
In Ada the clause with Standard; useStandard; is implicitly added to the start of each program unit. The specification for the package Standardis shown in appendix 0. This package contains definitions for +, -, *, /, etc. The package Standard can not be directly changed by a programmer.
Chapter Index |
The with and use clauses that appear before a specification of a package are implicitly included for the body of the package. If components of the with'ed and used packages are only used in the body of a package, then the clauses with and use need only be specified for the body. For example, in the class Account the specification of the package Class_accountdoes not use any components of Simple_io and thus can be written as :
package Class_account is -- rest of specification end Class_account; with Simple_io; use Simple_io; package body Class_account is -- rest of implementation end Class_account; |
One consequence of this approach is that the user of the package need not know what packages are used by the implementation code.
Chapter Index |
A user may wish to use packages that contain items with the same name. For example, a user of the class Class_accountalso requires to use the class Class_Account_other. In both classes the name of the type that is used to declare an instance of the class is Account. By prefixing the type name with the package name the conflict is resolved.
with Ada.Text_io, Class_account, Class_Account_other; use Ada.Text_io, Class_account, Class_Account_other; procedure main is my_account :Class_account.Account; other_account :Class_Account_other.Account; begin statement( my_account ); -- In Class_account statement( other_account ); -- In Class_Account_other end main; |
Note: Overload resolution is used to resolve which package the procedure statement is implemented in.
Chapter Index |
The methods in a class can either be inspectors, or mutators. The role of each of these types of methods is illustrated in the table below:
Method is a | Role of method | Example from class Account |
Inspector | Does not change the state of the object. | balance |
Mutator | Changes the state of the object. |
withdraw
deposit |
Chapter Index |
In the specification of the class Account seen in section 5.3.4 the type of Account is private. This restricts a user of an instance of the type to the following operations:
A user of the type is prevented from reading or changing the internal contents other than by the actions of methods in the class.
Chapter Index |
A user can be further restricted in the operations that they can perform on an instance of Account by declaring it as limited private. This removes the users ability to assign or compare an instance of the type by default. Naturally if in the class Account the comparison operations for equality or inequality are provided then these definitions will be used and will override the restriction. For example, if in the class Account the type Account where defined as limited private a user of an instance of an Account would be prevented from writing the following:
procedure devious is my_account : Account; other_account : Account; get : Money; begin deposit( my_account, 100.00 ); other_account := my_account; -- Copy and withdraw( other_account, 100.00, get ); -- Withdraw 100.00 other_account := my_account; -- Copy again and withdraw( other_account, 100.00, get ); -- Withdraw 100.00 end devious; |
If Account in the class Account had been made limited private its specifications would be:
package Class_account is type Account is limited private; -- Methods (functions and procedures) private type Account is limited record the.balance_of : Money := 0.00; -- Amount in account end record; end Class_account; |
Note: That the record declaration in the private part of the class is also of limited type.
In Ada 83 the use of limited in:
type Account is limited record
is not allowed.
The more traditional reason for making a type limited is that a copy operation will not produce the expected result for an instance of the type. Chapter 16 describes such a type that is built using dynamic storage.
Chapter Index |
The table below summarizes the allowable uses of private and limited private types.
Operation involving | private | limited private |
Assignment | Ok | Can not use |
Comparison using = and /= by default | Ok | Can not use |
Parameter passing | Ok | Ok |
Chapter Index |
For example, the following modified class Account uses both these approaches to initialize an object on declaration.
package Class_account is subtype Money is Float; subtype PMoney is Float range 0.0 .. Float'Last; type Account( number: Natural:= 0 ) is private; procedure statement( the:in Account ); procedure deposit ( the:in out Account; amount:in PMoney ); procedure withdraw( the:in out Account; amount:in PMoney; get:out PMoney ); function balance ( the:in Account ) return Money; procedure new_number( the: in out Account; n:in Natural ); function new_account( n:in Natural; amount:in PMoney:=0.0 ) return Account; private type Account( number: Natural:= 0) is record balance_of : Float := 0.00; end record; end Class_account; |
Chapter Index |
Here a type can be given a discriminant so that a whole family of types may be declared. The discriminant value is held in an instance of the type. Section 6.4 in Chapter 6 describes in more detail the use of discriminants.
For example, to set my_account with a specific account number the following code is written:
with Ada.Text_io, Class_account; use Ada.Text_io, Class_account; procedure main is my_account: Account(10001); begin deposit( my_account, 200.00 ); statement( my_account ); new_number( my_account, 10002 ); statement( my_account ); end main; |
Note: The discriminant value 10001 in the declaration of an instance of Account.
Which when run will produce:
Mini statement: Account # 10001 The amount on deposit is £200.00 Mini statement: Account # 10002 The amount on deposit is £200.00 |
Chapter Index |
The following restrictions however apply:
Thus the implementation of the procedure new_number that allocates a new account number is:
procedure new_number( the: in out Account; n: in Natural ) is begin the := Account'( n, the.balance_of ); end new_number; |
Note: The whole record structure needs to be changed to change the discriminant.
Chapter Index |
In this case the object is assigned an initial value when it is declared. For example, the following code sets my account with an account number and initial balance.
with Ada.Text_io, Class_account; use Ada.Text_io, Class_account; procedure main is my_account : Account := new_account( 10001, 20.0 ); begin statement( my_account ); end main; |
Which when run will produce:
Mini statement: Account # 10001 The amount on deposit is £20.00 |
Chapter Index |
The following restrictions however apply.
In addition the effect of the assignment statement, may have consequences that are undesirable. For an explanation of these consequences see section 0.0.
Chapter Index |
One of the applications on a PDA (Personnel Digital Assistant) is a PAM (Personnel Account Manager). The PAM provides facilities for recording the transactions that take place on the user's bank account. An example of the use of the PAM is shown below:
[a] Deposit [b] Withdraw [c] Balance Input selection: a Amount to deposit : 10.00 |
[a] Deposit [b] Withdraw [c] Balance Input selection: b Amount to withdraw : 4.60 |
[a] Deposit [b] Withdraw [c] Balance Input selection: c Balance is 5. 40 |
The program can be constructed using two classes: Account shown in section 5.3.4 and a new class TUI that will implement the text interface. The responsibilities of the class TUI are:
Method Responsibility menu Set up the menu that will be displayed to the user. Each menu item is described by a string. event Return the menu item selected by a user of the TUI. message Display a message to the user. dialogue Solicit a response from the user. |
The Ada specification of the class TUI is:
package Class_tui is type Menu_item is ( M_1, M_2, M_3, M_4, M_QUIT ); type TUI is private; procedure menu( the:in out TUI; m1,m2,m3,m4:in String ); function event( the:in TUI ) return Menu_item; procedure message( the:in TUI; mes:in String ); procedure dialogue(the:in TUI; mes:in String; res:out Float); procedure dialogue(the:in TUI; mes:in String; res:out Integer); private -- Not a concern of the client of the class end Class_tui; |
For example, if an instance of the TUI had been declared with:
screen : TUI; |
Then, to set-up the menu system:
[a] Print [b] Calculate Input selection: |
the following code sequence would be used:
menu( screen, "Print", "Calculate", "", "" ); |
Note: Null or empty menu items are not displayed.
A string may be of any length, however to store a string the receiving object must be of the correct size. Ada strings are fully discussed in section 7.8.
The user's response to this menu is elicited with the function event. The function event returns an enumeration representing the menu item selected. For example, if the user selected option [b] then the code:
case event( screen ) is when M_1 => -- Print when M_2 => -- Calculate |
associated with label M_2 would be obeyed.
Note: The selected menu item is indicated by an enumeration M_1 for menu item 1, M_2 for menu item 2, etc.
A programmer can display a message onto the TUI by using the procedure message which has the text to be output as its second parameter. Likewise a programmer can initiate a dialogue with the user by using the procedure dialogue that returns a floating point number. The TUI currently only supports dialogues that solicit a floating point number or integer number.
The fragment of code below illustrates the use of message and dialogue interactions in a program which converts miles to kilometres..
message ( screen, "Distance converter" ); dialogue( screen, "Enter distance in miles", miles ); message ( screen, "Distance in kilometres is " & Float'Image( miles * 1.6093 ) ); |
Note: The operator & concatenates two strings into a single string. For example, "Hello" & " " & "world" delivers the single string "Hello world".
In constructing the main program for the personnel account manager, a nested function float_image is used to simplify the construction of the program.
with Simple_io, Class_account, Class_tui; use Simple_io, Class_account, Class_tui; procedure main is user : Account; -- The users account screen : TUI; -- The display screen cash : Money; -- received : Money; -- |
The nested function float_image converts a floating point number into an Ada string. This function is provided so that the format of the number may be controlled.
function float_image( f:in Float ) return String is res : String( 1 .. 10 ); -- String of 10 characters begin put( res, f, aft=>2, exp=>0 ); -- 2 digits - NO exp return res; end float_image; |
Note: The declaration of a string of 10 characters, which is filled with the character representation for the floating point number res.
The procedure 'put( res, f, aft=>2, exp=>0 );' which converts a floating point number into a string.
The main body of the program processes the option selected by the user.
begin loop menu( screen, "Deposit", "Withdraw", "Balance", "" ); case event( screen ) is when M_1 => -- Deposit dialogue( screen, "Amount to deposit", cash ); if cash <= 0.0 then message( screen, "Must be >= 0.00" ); else deposit( user, cash ); end if; when M_2 => -- Withdraw dialogue( screen, "Amount to withdraw", cash ); if cash <= 0.0 then message( screen, "Must be >= 0.00" ); else withdraw( user, cash, received ); if received <= 0.0 then message( screen, "Not enough money" ); end if; end if; when M_3 => -- Balance message( screen, "Balance is " & float_image( balance(user)) ); when M_QUIT => -- Exit return; when others => -- Not used message( screen, "Program error"); -- oops end case; end loop; end main; |
Chapter Index |
The full specification for the class TUI is:
package Class_tui is type Menu_item is ( M_1, M_2, M_3, M_4, M_QUIT ); type TUI is private; procedure menu( the:in out TUI; m1,m2,m3,m4:in String ); function event( the:in TUI ) return Menu_item; procedure message( the:in TUI; mes:in String ); procedure dialogue(the:in TUI; mes:in String; res:out Float); procedure dialogue(the:in TUI; mes:in String; res:out Integer); private type TUI is record selection : Menu_item := M_QUIT; end record; end Class_tui; |
In the implementation of the class TUI the most complex method is menu. This method is implemented as a procedure that writes out the menu for the TUI and reads the users response. It will only complete when a valid response has been received from the user. In the implementation of the procedure the technique of procedural decomposition is used to simplify the code.
In procedural decomposition a large body of code is split into several procedures or functions. This helps to reduce complexity making construction and maintenance easier.
with Ada.Text_io; use Ada.Text_io; package body Class_tui is procedure menu( the:in out TUI; m1,m2,m3,m4:in String ) is selection : Character; valid_response : Boolean := FALSE;
|
As a user may inadvertently select a null menu item, the procedure set_response is used to disallow such an action.
procedure set_response(choice:in Menu_item; mes:in String) is begin if mes /= "" then -- Allowable choice the.selection := choice; valid_response := TRUE; end if; end set_response; |
The procedure display_menu_item displays onto the TUI only non null menu items.
procedure display_menu_item(prompt, name:in String) is begin if name/="" then put(prompt & name); new_line(2); end if; end display_menu_item; |
The main body of the procedure displays the menu on the screen and receives the selected menu choice from the user. If an invalid response is received the menu is re-displayed and the user is asked again to select a menu item.
begin while not valid_response loop display_menu_item( "[a] ", m1 ); display_menu_item( "[b] ", m2 ); display_menu_item( "[c] ", m3 ); display_menu_item( "[d] ", m4 ); put( "Input selection: "); get( selection ); skip_line; case selection is when 'a' | 'A' => set_response( M_1, m1 ); when 'b' | 'B' => set_response( M_2, m2 ); when 'c' | 'C' => set_response( M_3, m3 ); when 'd' | 'D' => set_response( M_4, m4 ); when 'e' | 'E' => set_response( M_QUIT, "Quit" ); when others => valid_response := FALSE; end case; if not valid_response then message( the, "Invalid response" ); end if; end loop; end menu; |
The function event returns the user's selection.
function event( the:in TUI ) return Menu_item is begin return the.selection; end; |
The procedure message writes a string onto the screen.
procedure message( the:in TUI; mes:in String ) is begin new_line; put( mes ); new_line; end message; |
The procedure dialogue solicits a response from the user.
procedure dialogue(the:in TUI; mes:in String; res:out Float) is begin new_line(1); put( mes & " : " ); get( res ); skip_line; end dialogue; procedure dialogue(the:in TUI; mes:in String; res:out Integer) is begin new_line(1); put( mes & " : " ); get( res ); skip_line; end dialogue; end Class_tui; |
Note: In this case the response must be a floating point number or an integer number. Other overloaded procedures can be provided for different forms of dialogue.