comp.lang.ada
 help / color / mirror / Atom feed
* Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG)
@ 1994-09-27 16:52 John Volan
  1994-09-27 18:48 ` Mark A Biggar
  1994-09-28 14:01 ` Norman H. Cohen
  0 siblings, 2 replies; 43+ messages in thread
From: John Volan @ 1994-09-27 16:52 UTC (permalink / 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.");
--------------------------------------------------------------------------------



^ permalink raw reply	[flat|nested] 43+ messages in thread

end of thread, other threads:[~1994-10-05 21:09 UTC | newest]

Thread overview: 43+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
1994-09-27 16:52 Mut. Recurs. in Ada9X w/o Breaking Encaps.? (LONG) John Volan
1994-09-27 18:48 ` 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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox