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 的能力?
这是我对 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;
正如 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
的情况下编译此函数,并且与 "="
没有关联,但这是一个编译器错误)。
是的,你当然可以做到。嗯,有点像。
但就像大多数 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 的孩子?
Type Item 不是标记记录。因此它不能在子包中扩展。
你的问题很混乱。您显示的包声明没有声明可以从任何点“直接”更改的“项目记录”对象(变量);它仅声明一个称为 Item 的“记录类型”和一些子程序。子包在运行时无法更改记录类型;它在编译时已修复。 也许您的示例不完整,并不能反映您的真正问题?