visibilityrecordada

Hiding record from child packages


package Parent is

   type Item is private;
   
   function Get return Item;
   
private
   
   type Item is
      record
         Value : Boolean;
      end record;
   
   procedure Set 
     (Object : Item;
      Value  : Boolean);

end Parent;

Please tell me how in this example to prevent changing the Item record from child packages directly, leaving the ability to call the private method Set?


Solution

  • As Jere has pointed out, this is a consequence of using child pkgs to provide programming by extension. Programming by extension is generally a bad idea, as it emphasizes ease of writing over ease of reading, and violates S/W-engineering principles.

    Jere presented the standard way to hide the actual type from child pkgs, using access types. This works, but as it involves manual memory management is error prone.

    A way to avoid this problem with programming by extension without using access types is to use ... more programming by extension:

    private -- Parent
       type Root is abstract tagged null record;
    
       function Equal (Left : in Root'Class; Right : in Root'Class) is
          (Left = Right);
    
       package Class_Holders is new Ada.Containers.Indefinite_Holders
          (Element_Type => Root'Class, "=" => Equal);
    
       type Item is record
          Value : Class_Holders.Holder;
       end record;
    end Parent;
    
    package body Parent is
       type Real_Item is new Root with record
          Value : Boolean;
       end record;
    

    You can store a Real_Item in a Holder. When retrieving the value, you have to convert it to Real_Item:

    R : Real_Item;
    V : Item;
    ...
    R.Value := True;
    V.Value.Replace_Element (New_Item => R);
    ...
    R := Real_Item (V.Value.Element);
    

    There are ways to use this approach in which Root can be an interface type, and others where it cannot. I always use an abstract tagged type to avoid having to remember which is which.

    The function Equal is needed because class-wide types have no primitive operations (note that GNAT will compile this without Equal and with no association for "=", but this is a compiler error).