Questions on OOP and preventing circular dependencies

Hi everyone, I’m learning Ada by doing a basic project that involves OOP. I come from a Java background so it’s what I’m accustomed to. I have a few questions about OOP and any support on them is appreciated.

  1. Am I correct in thinking the idea would be to make one of the packages be included using “limited with” as opposed to “with”. I then use an access type when I store that limited class inside the record of the other class. When I want to call subprograms from that access typed class, I have to do .all() and then the method? This approach is designed to avoid circular dependencies.

  2. For a one-many or many-many relationship, do I make a vector of the access (pointer) type and store all the many-side objects in there and perform the same .all() to actually use the methods of that object.

At the moment, when I’ve done “limited with” and made that class an access type. I don’t know how to make that a parameter in one of the subprograms in that same file. I get an error error: invalid use of untagged type "Passenger". My procedure is doing the following :

   procedure initialize_booking (b : in out Booking; flight : Unbounded_String; booker : Passengers.Passenger) is
      b.ID := nextID; := nextSeat;
      b.flight := flight;
      b.booker := access booker;

      nextID := nextID + 1;
   end initialize_booking;
  1. What is the best practice for string management? I’ve been having to use unbounded strings and I find myself having to perform conversions sometimes from a regular String to an unbounded.

I think the Association between two classes might be worthy of being a package in its own right.

Consider Clinic & Patient. There’s at least one many-to-many Association between these two, namely Appointment. Clearly an Appointment is between a Clinic and a Patient, but there’s more to it than that: the Date, the staff who are involved, the tests ordered, …

The same sort of thing could apply to one-to-many Associations, too.

So, I’d think that using limited with and keeping the association within either participant might be a possible optimisation (particularly for the M:M case).

But, I am coming from a Shlaer-Mellor OOA point of view!

Thanks for getting back to me! That’s a great idea for this but I really prefer only doing that for many-many since sometimes there are really simple one-many associations. I’ve still been unsuccessful in trying to use limited with and as I come from a Java background, the association by holding a reference on each side feels natural to me. I’ve seen that I need to mess around with access types a bit. Please let me know if you have any insight on this as it’s been a real struggle to get it working. Thanks!

If you have

limited with Passenger;
package Flight is
   procedure Book (Pax : Passenger.Handle);
end Flight;

then the body of Flight has to fully with Passenger;, or all it can see is types (Passenger.Handle), not any operations.

There’s lots of info in the Ada 2005 Rationale, section 4.2.

1 Like

As much as possible, I would avoid limited with. I have worked with codebases with millions of lines of code and not a single limited with.
The reason for that is that it can quickly result in tangled code, where refactorings are almost impossible because things are so strongly related.

In general, too, try to avoid access types when you can (but here I have thousands of them in the code I work on, so that’s not possible). For instance:

  • Use Unbounded_String rather than String_Access (given your question, that’s what you are doing already). That simplifies memory management. There are multiple string libraries out there, but starting with unbounded_string is fine. In some cases you can use a String_Access directly if you know exactly when to free it.
  • use Ada.Containers.Indefinite_Vector rather than an Ada.Containers.Vectors containing access types, and let the contain take care of memory management. This is only applicable when you own the data and nobody has an access pointing to it (since the object might move in memory).

Perhaps one approach in your case to avoid the limited with would be something like

    type Booker_Interface is new interface;
    --  could be a passenger, could be a travel agency, ...

    type Booking is tagged private;

    procedure Reserve (Self : in out Booking; By : Booker_Interface'Class);

and in another package

    type Passenger is new Booker with ...

Using interfaces should be familiar to you, coming from Java.

1 Like

OOP aside, the separation of specification vs. body for packages gives you a fair amount of flexibility:

package a is
  procedure p;

with b;
package body a is
  procedure p is null;

package b is
  procedure p;

with a;
package body b is
  procedure p is null;