隐藏子包中的记录

问题描述 投票:0回答:5
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;

请告诉我在此示例中如何防止直接更改子包中的 Item 记录,而保留调用私有方法 Set 的能力?

visibility record ada
5个回答
3
投票

这是我对 Ada 的抱怨之一(只是极少数的抱怨之一),它允许人们简单地通过为你的包制作一个子包来绕过隐私。 我没有搞乱私有子包来看看是否可以让某些东西工作,但如果你对堆分配没问题的话,PIMPL 模式确实可以在 Ada 中工作。

基本上,您在包规范中创建一个不完整的类型,并在私有记录声明中使用该类型的访问参数。 规范不知道记录不完整类型是什么样子,但由于您只使用它的访问类型,规范将编译。还应该隐藏所有所需的私有操作,例如仅设置为包体。

然后在包体中完全定义不完整类型,我建议使用 Ada.Finalization 来确保参数始终完全分配和释放。

我将给出一个完全可编译的示例(使用在线教程点 ada 编译器进行测试)。

我也不知道如何处理您的 Get 操作,因此只是将其默认为某个值,并添加了 Get_Value 操作来获取布尔值。 您可以根据需要删除/调整它。

这不是最通用的解决方案,但它是我在 Ada 中找到的可行方案。 再说一遍,我还没有探索过“私有”子包来看看它们是否可以以这种方式发挥作用,所以也许可以探索一下。

with Ada.Finalization;
with Ada.Unchecked_Deallocation;

with Ada.Text_IO; use Ada.Text_IO;
procedure Hello is
    
    package Parent is
    
        type Item is tagged private;
        
        function Get return Item;
        function Get_Value(Self : in Item) return Boolean;
       
    private
    
        type Private_Item;
        type Private_Access is access Private_Item;
       
        type Item is new Ada.Finalization.Controlled with record
            Impl : Private_Access := null;
        end record;
        
        overriding procedure Initialize(Self : in out Item);
        overriding procedure Finalize(Self : in out Item);
        overriding procedure Adjust(Self : in out Item);
    
    end Parent;
    
    package body Parent is
    
        type Private_Item is record
            Value : Boolean := False;
        end record;
        
        procedure Set 
            (Object : in out Item;
             Value  : Boolean)
        is begin
            Object.Impl.Value := Value;
        end Set;
        
        -- What is this supposed to do????
        function Get return Item is (Ada.Finalization.Controlled with Impl => new Private_Item);
        
        function Get_Value(Self : in Item) return Boolean is
        begin
            return Self.Impl.value;  -- raises null exception if not initialized
        end Get_Value;
            
             
        procedure Initialize(Self : in out Item) is
        begin
            if Self.Impl = null then
                Self.Impl := new Private_Item;
            end if;
        end Initialize;

        procedure Adjust(Self : in out Item) is
            Temp : Private_Access := Self.Impl;
        begin
            if Temp /= null then
                Self.Impl := new Private_Item'(Temp.all);
            end if;
        end Adjust;
        
        procedure Free is new Ada.Unchecked_Deallocation(Private_Item, Private_Access);
        
        procedure Finalize(Self : in out Item) is
        begin
            if Self.Impl /= null then
                Free(Self.Impl);
            end if;
        end Finalize;
    
    end Parent;
    
    I : Parent.Item;

begin
    Put_Line("Hello, world!");
    Put_Line(Boolean'Image(I.Get_Value));
end Hello;

1
投票

正如 Jere 所指出的,这是使用子 pkg 来提供扩展编程的结果。扩展编程通常是一个坏主意,因为它强调易于编写而不是易于阅读,并且违反了软件工程原则。

Jere 提出了使用访问类型对子 pkg 隐藏实际类型的标准方法。这是可行的,但由于它涉及手动内存管理,因此很容易出错。

通过扩展编程而不使用访问类型来避免此问题的一种方法是使用...更多扩展编程:

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;

您可以将

Real_Item
存储在
Holder
中。检索值时,必须将其转换为
Real_Item
:

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

有多种方法可以使用此方法,其中 Root 可以是接口类型,而其他方法则不能。我总是使用抽象标记类型来避免记住哪个是哪个。

需要函数

Equal
,因为类范围类型没有基元操作(请注意,GNAT 将在没有
Equal
的情况下编译此函数,并且与
"="
没有关联,但这是一个编译器错误)。


1
投票

是的,你当然可以做到。嗯,有点像。

但就像大多数 Ada 式的事情一样,它需要一点思考和计划。

这是一种方法(唯一的方法?)

Ada Hierarch Example

相应的声明是,

package GrandParent is
   type Item is private;
private
   type Item is
      record
         Value : Boolean;
      end record;
end GrandParent;

package GrandParent.Parent is
   function Get
     (The_Item : in Item)
      return Boolean;
end GrandParent.Parent;

private package GrandParent.Child1 is
   procedure Set
     (The_Item : in out Item;
      Value    : in     Boolean);
end GrandParent.Child1;

封装体是,

package body GrandParent.Child1 is
   
   procedure Set
     (The_Item : in out Item;
      Value    : in     Boolean)
   is
   begin
      The_Item.Value := Value;
   end Set;
   
end GrandParent.Child1;

private with GrandParent.Child;

package body GrandParent.Parent is
   
   function Get
     (The_Item : in Item)
      return Boolean
   is
     (The_Item.Value);
   
   procedure Set
     (The_Item : in out Item;
      Value    : in     Boolean)
   is
   begin
      GrandParent.Child.Set
        (The_Item => The_Item,
         Value    => Value);
   end Set;
   
end GrandParent.Parent;

如果你尝试拥有,

(private) with GrandParent.Child;
package GrandParent.Parent.Child is
end GrandParent.Parent.Child;

这会引发编译时错误,即当前单元也必须是 GrandParent 的直接后代,从而有效地使 GrandParent.Child1 包私有于 GrandParent.Parent。

GrandParent 的客户端也无法看到 GrandParent.Child1。但是,GrandParent 的其他子级将具有与 GrandParent.Parent 相同的可见性

这就是隐藏 Set 子程序的方法。如果您想对子包隐藏私有类型怎么办?

首先,这可能是有问题的,因为具有私有类型的包的子包被设计为与该类型完全交互,因为正如其他人所描述的,子包是为了扩展其各自父包的功能。

如果您想这样做,那么最好的选择是将类型 Item 以及 Get 和 Set 例程隐藏到 GrandParent.Child 中,以便只有 GrandParent.Parent 可以看到它们(在其私有主体中)并公开您想要的任何功能想要允许 GrandParent.Parent 的孩子在 GrandParent.Parent 包中。

但是,我不确定这是否特别有用。一个问题 - 如果 Parent 的孩子不应该访问 Item 的内部运作,为什么他们是 Parent 的孩子?


0
投票

Type Item 不是标记记录。因此它不能在子包中扩展。


0
投票

你的问题很混乱。您显示的包声明没有声明可以从任何点“直接”更改的“项目记录”对象(变量);它仅声明一个称为 Item 的“记录类型”和一些子程序。子包在运行时无法更改记录类型;它在编译时已修复。 也许您的示例不完整,并不能反映您的真正问题?

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.