From: jgv@swl.msd.ray.com (John Volan)
Subject: Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG)
Date: Tue, 27 Sep 1994 16:52:03 GMT
Date: 1994-09-27T16:52:03+00:00 [thread overview]
Message-ID: <1994Sep27.165203.9192@swlvx2.msd.ray.com> (raw)
As a long-time Ada evangelist and self-styled acolyte of Team-Ada (ahh gimme
that ol' time language religion! :-), I'm quite pleased with the new OO
features of Ada 9X. One of the things I like is that Ada 9X does *NOT* follow
the crowd of other OO languages in one very important particular: *NO* attempt
was made to merge the features of packages and types into some kind of new
"class" construct. Issues of modularity, encapsulation, information hiding,
interface control, scoping, inter-module dependency, namespace control, generic
templating, etc., are still the province of packages. Issues of data
abstraction, strong typing, type derivation and inheritance, etc., are still
the province of types. And the place where these two areas intersect is
still the same: private types.
IMHAARO (In My Humble And Admittedly Religious Opinion :-) this kind of
separation of concerns is extremely beneficial. I've looked at other
OO languages that use the "class" as both the unit of data abstraction and
the unit of modularity (mostly Smalltalk, C++, Eiffel), and my gut reaction
is that this attempt to merge features into a single construct leads to a lot
of unnecessary "baroqueness," in both syntax and semantics. C++'s constructors
and destructors, and Eiffel's "anchor" and "like" mechanisms, come to mind as
prime examples of this. (Please, no fire and brimstone from you folks in the
other religions -- it's MHAARO after all, and I'd certainly welcome hearing
yours... :-)
However, there is one specific kind of situation that I'm having difficulty
mapping into Ada 9X: How can you achieve mutual recursion between two classes
of object without breaking encapsulation? In other words, you have two classes
of objects that must "know" about each other's interfaces, but you don't want
to have to expose their implementations to each other in order to do that.
Consider a simple example where we have two classes related in a simple
one-to-one association:
Employee -- occupies -- Office
Of course, associations are conceptually bi-directional, so this could be
expressed equivalently as:
Office -- is occupied by -- Employee
If our application needs to traverse this association only in one direction or
the other, then we are free to choose a design where only one of the classes
has to "know" about the other class. It's fairly straightforward to map such
a design into an Ada coding style that encapsulates each class as a private
type declared in its own package. One class "knowing" about the other
translates into one package importing the other via a "with" clause. (We can
sort of mimic this kind of Ada modularization in C++ by putting each C++ class
in a separate header file and having one header file "#include" the other.)
For instance, suppose our application needs to be able to start from a given
Employee object and look up its occupied Office object. We could have:
----------------------------------------------------------------------
with ...; -- other stuff, but no knowledge about the Employee package
package Office is
type Object is tagged limited private; -- perhaps we'll distinguish
-- different subclasses of Office
type Pointer is access all Object'Class;
None : constant Pointer := null;
... -- various subprograms involving that "other stuff"
private
type Object is tagged
record
... -- various components involving that "other stuff"
end record;
end Office;
----------------------------------------------------------------------
with ...; -- other stuff
with Office; -- Employee class "knows" about Office class
package Employee is
type Object is tagged limited private; -- perhaps we'll distinguish
-- different subclasses of Employee
type Pointer is access all Object'Class;
None : constant Pointer := null;
... -- various subprograms involving that "other stuff"
function Office_Occupied_By (The_Employee : in Employee.Object)
return Office.Pointer;
procedure Occupy (The_Employee : in out Employee.Object;
The_Office : in Office.Pointer);
...
private
type Object is tagged
record
... -- various components involving that "other stuff"
Its_Occupied_Office : Office.Pointer;
...
end record;
end Employee;
----------------------------------------------------------------------
The equivalent in C++ might be:
//////////////////////////////////////////////////////////////////////
// File: Office.h
#ifndef _Office_h
#define _Office_h
#include " ... other stuff ... "
class Office {
public:
... // various member functions involving that "other stuff"
private:
... // various data members involving that "other stuff"
};
#endif _Office_h
//////////////////////////////////////////////////////////////////////
// File: Employee.h
#ifndef _Employee_h
#define _Employee_h
#include " ... other stuff ... "
#include "Office.h"
class Employee {
public:
... // various member functions involving that "other stuff"
Office* occupiedOffice () const;
void occupy (Office* theOffice);
...
private:
... // various data members involving that "other stuff"
Office* itsOccupiedOffice;
..
};
#endif _Employee_h
//////////////////////////////////////////////////////////////////////
Conversely, if our application needs to be able to start from a given Office
object and look up the Employee object which occupies it, then we just need to
flip the dependency around:
----------------------------------------------------------------------
with ...; -- other stuff, but no knowledge about the Office package
package Employee is
type Object is tagged limited private; -- perhaps we'll distinguish
-- different subclasses of Employee
type Pointer is access all Object'Class;
None : constant Pointer := null;
... -- various subprograms involving that "other stuff"
private
type Object is tagged
record
... -- various components involving that "other stuff"
end record;
end Employee;
----------------------------------------------------------------------
with ...; -- other stuff
with Employee; -- Office class "knows" about Employee class
package Office is
type Object is tagged limited private; -- perhaps we'll distinguish
-- different subclasses of Office
type Object is tagged limited private;
type Pointer is access all Object'Class;
None : constant Pointer := null;
... -- various subprograms involving that "other stuff"
function Employee_Occupying (The_Office : in Office.Object)
return Employee.Pointer;
procedure Occupy (The_Office : in out Office.Object;
The_Employee : in Employee.Pointer);
...
private
type Object is tagged
record
... -- various components involving that "other stuff"
Its_Occupying_Employee : Employee.Pointer;
...
end record;
end Office;
----------------------------------------------------------------------
The equivalent in C++ might be:
//////////////////////////////////////////////////////////////////////
// File: Employee.h
#ifndef _Employee_h
#define _Employee_h
#include " ... other stuff ... "
class Employee {
public:
... // various member functions involving that "other stuff"
private:
... // various data members involving that "other stuff"
};
#endif _Employee_h
//////////////////////////////////////////////////////////////////////
// File: Office.h
#ifndef _Office_h
#define _Office_h
#include " ... other stuff ... "
#include "Employee.h"
class Office {
public:
... // various member functions involving that "other stuff"
Employee* occupyingEmployee () const;
void beOccupiedBy (Employee* theEmployee);
...
private:
... // various data members involving that "other stuff"
Employee* itsOccupyingEmployee;
..
};
#endif _Office_h
//////////////////////////////////////////////////////////////////////
In either of these scenarios, we have been able to maintain complete
encapsulation of both object classes. The class in the second package is
always required to deal with the class from the first package strictly through
the interface that appears in the public part of the first package.
But what if we need to be able to traverse the association in *both*
directions? The most straightforward design to meet this requirement would
make the data types for Employee and Office mutually recursive: Each class
of object would contain a reference [1] to an object of the other class. This
is easy enough to do (in either Ada or C++) by "forward declaring" the types
so that both of them can "know" about each other -- but it appears that we'd
have to relinquish the idea of putting the two classes in separate packages.
Here's my attempt at mutually recursive classes in Ada 9X:
----------------------------------------------------------------------
with ... ; -- other stuff needed by Employee
with ... ; -- other stuff needed by Office
package Employee_And_Office is
type Employee is tagged limited private; -- already acts as a forward decl
type Employee_Pointer is access all Employee'Class;
No_Employee : constant Employee_Pointer := null;
... -- Employee subprograms involving that "other stuff"
type Object is tagged limited private; -- already acts as a forward decl
type Office_Pointer is access all Office'Class;
No_Office : constant Office_Pointer := null;
... -- Office subprograms involving that "other stuff"
function Office_Occupied_By (The_Employee : in Employee)
return Office_Pointer;
function Employee_Occupying (The_Office : in Office)
return Employee_Pointer;
procedure Occupy (The_Office : in Office_Pointer;
The_Employee : in Employee_Pointer);
-- Hmm ... does this count as a primitive/dispatching operation for
-- *both* tagged types, and therefore a compiler error?
private
type Employee is tagged
record
... -- various components involving that "other stuff"
Its_Occupied_Office : Office_Pointer;
...
end record;
type Office is tagged
record
... -- various components involving that "other stuff"
Its_Occupying_Employee : Employee_Pointer;
...
end record;
end Employee_And_Office;
----------------------------------------------------------------------
Here's my equivalent C++:
//////////////////////////////////////////////////////////////////////
// File: EmployeeAndOffice.h
#ifndef _EmployeeAndOffice_h
#define _EmployeeAndOffice_h
#include " ... other stuff needed by Employee ... "
#include " ... other stuff needed by Office ... "
class Employee; // forward declaration
class Office; // forward declaration
class Employee {
public:
... // various member functions involving that "other stuff"
Office* occupiedOffice () const;
friend void occupy (Office* theOffice, Employee* theEmployee);
...
private:
... // various data members involving that "other stuff"
Office* itsOccupiedOffice;
..
};
class Office {
public:
... // various member functions involving that "other stuff"
Employee* occupyingEmployee () const;
friend void occupy (Office* theOffice, Employee* theEmployee);
...
private:
... // various data members involving that "other stuff"
Employee* itsOccupyingEmployee;
..
};
#endif _EmployeeAndOffice_h
//////////////////////////////////////////////////////////////////////
In both the Ada 9X and C++ versions we do need to break encapsulation a bit:
The two classes had to be stuck in the same package, and the Occupy procedure,
which establishes the association between an Employee and an Office, needs
to have visibility to the implementations of both classes (in the C++ version,
it's a "friend" of both classes). However, in the C++ version, that's the
*only* point where we need to break encapsulation. If Employee and Office
objects need to interface each other in any other way, they have to go through
the public member functions. In the Ada version, on the other hand, having
both types in the same package broke encapsulation between them once and for
all. Any Employee subprogram can exploit detailed knowledge of the
implementation of the Office type, and vice versa. (Of course, we still
haven't broken encapsulation with respect to any *other* classes -- clients of
Employee and Office are still restricted to using their public interfaces.)
So my question is: Is there some other arrangement of Ada 9X packages (perhaps
involving the new hierarchical library mechanism and child units) that allows
for this kind of mutual recursion, and yet preserves the same amount of
encapsulation that the C++ version does?
Thanks in advance for any help,
-- John Volan
FOOTNOTE:
[1] In the above discussion, I used the term "reference to an object" as an
attempt to generalize the notion of a type of data that does not represent the
object itself, but which in some way "designates" or "refers to" or "expresses
the identity of" the object. The most obvious implementation of this notion
is pointers (Ada access values), and this implementation is assumed in my
code examples above.
However, this is not the only possibility. Numeric indexes to a statically
allocated array of objects can act as "identification numbers" for those
objects, and this leads to a possible solution of the problem I posed above:
You could create packages that just introduce the identification-number types,
then use them to introduce the object-type packages. Now, where those
statically-allocated arrays of objects would have to go, that's another
question. This solution is obviously more complicated, and less generally
applicable than pointers. So I'd still like to know of any better solution
that might exist.
--------------------------------------------------------------------------------
-- Me : Person := (Name => "John Volan",
-- Company => "Raytheon Missile Systems Division",
-- E_Mail_Address => "jgv@swl.msd.ray.com",
-- Affiliation => "Enthusiastic member of Team Ada!",
-- Humorous_Disclaimer => "These opinions are undefined " &
-- "by my employer and therefore " &
-- "any use of them would be " &
-- "totally erroneous.");
--------------------------------------------------------------------------------
next reply other threads:[~1994-09-27 16:52 UTC|newest]
Thread overview: 43+ messages / expand[flat|nested] mbox.gz Atom feed top
1994-09-27 16:52 John Volan [this message]
1994-09-27 18:48 ` Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) Mark A Biggar
1994-09-29 1:46 ` John Volan
1994-09-29 13:57 ` Tucker Taft
1994-09-29 17:20 ` Bjarne Stroustrup <9758-26353> 0112760
1994-09-30 1:38 ` Tucker Taft
1994-09-30 12:33 ` Bjarne Stroustrup <9758-26353> 0112760
1994-09-29 18:37 ` John Volan
1994-09-29 19:34 ` David Weller
1994-09-30 22:13 ` John Volan
1994-10-02 3:31 ` Andrew Lees
1994-09-30 1:47 ` Tucker Taft
1994-09-30 13:30 ` John Volan
1994-09-29 18:10 ` R. William Beckwith
1994-10-03 0:33 ` Cyrille Comar
1994-09-28 14:01 ` Norman H. Cohen
1994-09-29 2:12 ` John Volan
1994-09-29 14:01 ` Tucker Taft
1994-09-29 18:37 ` Norman H. Cohen
1994-09-29 9:48 ` Magnus Kempe
1994-09-29 13:10 ` Magnus Kempe
1994-09-29 18:05 ` Tucker Taft
1994-09-30 10:20 ` Mut. Recurs. in Ada9X w/o Breaking Encaps.? Magnus Kempe
1994-09-30 13:22 ` Tucker Taft
1994-10-01 1:24 ` Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) Adam Beneschan
1994-10-01 12:01 ` Magnus Kempe
1994-10-01 18:43 ` Mark A Biggar
1994-10-02 16:41 ` John Volan
1994-10-02 23:33 ` Matt Kennel
1994-10-03 8:07 ` Mut. Recurs. in Ada9X w/o Breaking Encaps.? Magnus Kempe
1994-10-03 12:14 ` Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) Robert I. Eachus
1994-10-04 2:12 ` R. William Beckwith
1994-10-04 16:00 ` John Volan
1994-10-05 11:42 ` Robert I. Eachus
1994-10-05 21:09 ` Matt Kennel
1994-10-03 20:29 ` Harry Koehnemann
1994-09-29 13:35 ` John Volan
1994-09-30 20:27 ` Norman H. Cohen
1994-10-01 1:47 ` John Volan
1994-10-01 20:44 ` Tucker Taft
1994-10-03 11:29 ` Robert I. Eachus
1994-09-30 22:46 ` Matt Kennel
1994-10-01 2:11 ` John Volan
replies disabled
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox