【问题标题】:Hiding record from child packages从子包中隐藏记录
【发布时间】:2021-10-20 14:35:28
【问题描述】:
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;

请告诉我在这个示例中如何防止直接从子包更改项目记录,留下调用私有方法 Set 的能力?

【问题讨论】:

    标签: visibility record ada


    【解决方案1】:

    这是我对 Ada 的抱怨之一(只是极少数之一),它允许人们通过制作您的包裹的子包裹来绕过隐私。我没有弄乱私有子包来看看我是否可以使某些东西起作用,但是如果您对堆分配没问题,那么 PIMPL 模式在 Ada 中确实有效。

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

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

    我将给出一个完全可编译的示例(使用在线 tutorialspoint 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);
        
        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 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;
    

    【讨论】:

      【解决方案2】:

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

      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 且与"=" 没有关联的情况下编译它,但这是编译器错误)。

      【讨论】:

      • 是的,我的主要好处是它也适用于有限类型,而 Indefinite_Holders 仅适用于非有限类型。
      【解决方案3】:

      是的,当然可以。嗯,有点。

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

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

      相应的声明是,

      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 的孩子?

      【讨论】:

      • 回答你的最后一个问题:我不认为 OP 实际上想要使用子包,但担心另一个程序员会制作一个子包来获取底层私有数据结构和方法并成为能够随心所欲地对付他们。我认为他们最终是在寻找如何创建满足“私有”的一般编程含义而不是“私有”的 Ada 含义的类型的部分,后者更接近于 C++ 和 Java 等语言中的“受保护”(与受保护的 Ada 含义)。
      【解决方案4】:

      类型项目不是标记记录。因此它不能在子包中扩展。

      【讨论】:

      • 我需要防止不扩展Item,而是从子包中更改这种类型的对象的字段。
      【解决方案5】:

      你的问题很困惑。您显示的包声明未声明可以从任何点“直接”更改的“项目记录”对象(变量);它只声明了一个名为Item 的记录类型 和一些子程序。子包在运行时无法更改记录类型;它在编译时修复。

      也许您的示例不完整,并不能反映您的实际问题?

      【讨论】:

      • 我的意思是,如果子包有一个 Item 实例,例如从 Get 函数获得,那么它可以直接更改其字段,绕过 Set 函数,这就是为此而设计的。
      • 需要有子包吗?只需将 set 过程的声明移动到包的公共区域并“与”包父级“一起”。 parent 的 public 部分的内容变得可见,但 parent 的 private 部分不可见。
      猜你喜欢
      • 1970-01-01
      • 2021-05-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-30
      • 1970-01-01
      相关资源
      最近更新 更多