ADA 95教程 高级特性 高级包和私有类型主题
这是针对大型项目的
当有几个程序员作为一个团队一起工作时,本章中的材料用于大型项目。在这种情况下,无论使用何种编程语言,都需要不同程序员之间进行良好的通信。如果Ada是为项目选择的语言,并且仔细遵循本章中规定的原则,那么所需的通信量可以最小化。当然,这些技术也可以被一个单独的程序员用来提高代码的质量,特别是当它是一个大型程序时。
两个好程序员
在本章中,我们将假设我们有一个由两个程序员开发的项目,他们都很敏锐,并且精通如何开发高质量的软件。我们将跟随示例程序,因为他们开发了一个简单的系统来添加或减去三组数字。其中一个被指派负责编写一个包,另一个程序员可以用这个包对任意结构的三个数字组进行加法或减法运算。
当然,这是一个微不足道的问题,但它将说明信息隐藏的概念,这在大型项目中是非常必要的。
第一个解决方案
Example program ------> e_c21_p1.ada
-- Chapter 21 - Program 1 -- This package uses a data structure composed of three INTEGER -- variables. It allow the user to add two structures, component -- by component, or subtract component by component. Provision is -- also made to build a structure from three numbers, or decompose -- a structure into its components. package Three is type DATA_STRUCTURE is record Value1 : INTEGER; Value2 : INTEGER; Value3 : INTEGER; end record; function "+"(Data1, Data2 : DATA_STRUCTURE) return DATA_STRUCTURE; function "-"(Data1, Data2 : DATA_STRUCTURE) return DATA_STRUCTURE; function Build_Structure(Val1, Val2, Val3 : INTEGER) return DATA_STRUCTURE; procedure Decompose(Data1 : DATA_STRUCTURE; Val1, Val2, Val3 : out INTEGER); end Three; package body Three is function "+"(Data1, Data2 : DATA_STRUCTURE) return DATA_STRUCTURE is Temp : DATA_STRUCTURE; begin Temp.Value1 := Data1.Value1 + Data2.Value1; Temp.Value2 := Data1.Value2 + Data2.Value2; Temp.Value3 := Data1.Value3 + Data2.Value3; return Temp; end "+"; function "-"(Data1, Data2 : DATA_STRUCTURE) return DATA_STRUCTURE is Temp : DATA_STRUCTURE; begin Temp.Value1 := Data1.Value1 - Data2.Value1; Temp.Value2 := Data1.Value2 - Data2.Value2; Temp.Value3 := Data1.Value3 - Data2.Value3; return Temp; end "-"; function Build_Structure(Val1, Val2, Val3 : INTEGER) return DATA_STRUCTURE is Temp : DATA_STRUCTURE; begin Temp.Value1 := Val1; Temp.Value2 := Val2; Temp.Value3 := Val3; return Temp; end Build_Structure; procedure Decompose(Data1 : DATA_STRUCTURE; Val1, Val2, Val3 : out INTEGER) is begin Val1 := Data1.Value1; Val2 := Data1.Value2; Val3 := Data1.Value3; end Decompose; end Three; -- This program exercises the package Three as an illustration. with Ada.Text_IO; use Ada.Text_IO; with Three; use Three; procedure NoPrivat is My_Data, Extra_Data : DATA_STRUCTURE; begin My_Data := Build_Structure(3, 7, 13); Extra_Data := Build_Structure(-4, 77, 0); My_Data := My_Data + Extra_Data; if My_Data /= Extra_Data then Put_Line("The two structures are not equal."); end if; My_Data := Extra_Data; if My_Data = Extra_Data then Put_Line("The two structures are equal now."); end if; My_Data.Value1 := My_Data.Value1 + 13; end NoPrivat; -- Result of execution -- The two structures are not equal. -- The two structures are equal now.
名为e_c21_p1.ada的示例文件说明了解决该问题的第一次尝试,但是它没有实践任何信息隐藏。第1行到第69行表示包编写器的工作,第74行到第101行由包的用户编写。包编写器使用一条记录来存储这三个数字,因为他在规范包中声明了该记录,所以他允许用户完全访问该记录以供自己使用。
包编写器通过提供三个函数和一个过程,为用户提供了添加相应元素、减去相应元素和两个附加功能的能力。名为Build_Structure的函数允许用户在给定单个组件的情况下构建新的结构,名为Decompose的过程允许用户将元素分离为各自的值。我们可以补充一点,这个包是两个程序员在会议上就系统必须做什么以及它们之间的接口必须是什么达成一致的结果。包规范是定义程序员之间接口所需的唯一信息。注意第3行到第7行中的注释,它们清楚而完整地定义了包的功能。
用户希望更改变量My_Data的一个值,并且由于他有整个可用的结构,他在第99行中通过绕过包编写器提供的可用结构处理方法进行了欺骗。他觉得自己省了一些指令和一点执行时间,所以这一点点作弊是有道理的。第99行是完全合法的,但是由于项目的巨大规模,在这种情况下可能会引起问题。回想一下,他以前曾同意只使用包编写器提供的功能,因为包编写器正在向另一个大型项目提供相同的包,并且必须与两个用户保持一致。
出现问题
为了提高效率和与其他项目的一致性,包编写者决定将存储数据的方式从三元素记录更改为三元素数组。当这个修改到位并经过测试后,用户突然发现他的程序将不再编译,即使他的程序中没有任何更改。唯一的问题是99行,它已经正常工作了好几个月了,但正如我们前面所说的,实际上是在用户和包编写器商定的接口上作弊。信息隐藏本来可以避免这个问题,在现实系统中,在一个包中进行看似很小的更改之后,可能会导致数百个编译器错误。
在这种情况下,用户作弊,最终导致了一个相当大的问题。我们要防止他作弊。当然,我们可以制定一个项目规则,说“没有作弊”,但我们将很快定义一种方法,让编译器为我们执行规则。使用“作弊”这个词只是为了增强故事的真实性。实际上,这更多的是用户的疏忽。在一天14个小时的编程之后进行调试时,很容易忘记之前关于接口的协议,并像第99行那样直接使用一些数据。我们需要一种方法让编译器提醒我们不要这样做。
很明显,在实际的大型系统中,被调用的包和调用的包不在同一个文件中,而是分别编译的。它们的结合使编译和执行变得更容易,此时您应该这样做。
更好的方法,私人类型
Example program ------> e_c21_p2.ada
-- Chapter 21 - Program 2 -- This package uses a data structure composed of three INTEGER -- variables. It allow the user to add two structures, component -- by component, or subtract component by component. Provision is -- also made to build a structure from three numbers, or decompose -- a structure into its components. package Three is type DATA_STRUCTURE is private; function "+"(Data1, Data2 : DATA_STRUCTURE) return DATA_STRUCTURE; function "-"(Data1, Data2 : DATA_STRUCTURE) return DATA_STRUCTURE; function Build_Structure(Val1, Val2, Val3 : INTEGER) return DATA_STRUCTURE; procedure Decompose(Data1 : DATA_STRUCTURE; Val1, Val2, Val3 : out INTEGER); private type DATA_STRUCTURE is record Value1 : INTEGER; Value2 : INTEGER; Value3 : INTEGER; end record; end Three; package body Three is function "+"(Data1, Data2 : DATA_STRUCTURE) return DATA_STRUCTURE is Temp : DATA_STRUCTURE; begin Temp.Value1 := Data1.Value1 + Data2.Value1; Temp.Value2 := Data1.Value2 + Data2.Value2; Temp.Value3 := Data1.Value3 + Data2.Value3; return Temp; end "+"; function "-"(Data1, Data2 : DATA_STRUCTURE) return DATA_STRUCTURE is Temp : DATA_STRUCTURE; begin Temp.Value1 := Data1.Value1 - Data2.Value1; Temp.Value2 := Data1.Value2 - Data2.Value2; Temp.Value3 := Data1.Value3 - Data2.Value3; return Temp; end "-"; function Build_Structure(Val1, Val2, Val3 : INTEGER) return DATA_STRUCTURE is Temp : DATA_STRUCTURE; begin Temp.Value1 := Val1; Temp.Value2 := Val2; Temp.Value3 := Val3; return Temp; end Build_Structure; procedure Decompose(Data1 : DATA_STRUCTURE; Val1, Val2, Val3 : out INTEGER) is begin Val1 := Data1.Value1; Val2 := Data1.Value2; Val3 := Data1.Value3; end Decompose; end Three; -- This program exercises the package Three as an illustration. with Ada.Text_IO; use Ada.Text_IO; with Three; use Three; procedure Privat1 is My_Data, Extra_Data : DATA_STRUCTURE; Temp : DATA_STRUCTURE; begin My_Data := Build_Structure(3, 7, 13); Extra_Data := Build_Structure(-4, 77, 0); My_Data := My_Data + Extra_Data; if My_Data /= Extra_Data then Put_Line("The two structures are not equal."); end if; My_Data := Extra_Data; if My_Data = Extra_Data then Put_Line("The two structures are equal now."); end if; -- The following line is illegal with the private type. -- My_Data.Value1 := My_Data.Value1 + 13; Temp := Build_Structure(13, 0, 0); My_Data := My_Data + Temp; end Privat1; -- Result of execution -- The two structures are not equal. -- The two structures are equal now.
检查名为e_c21_p2.ada的程序,以获取强制用户遵循规则的private 类型的示例。在包规范中,类型DATA_STRUCTURE被定义为private类型,Ada中的一个保留字,这意味着类型名可供用户用于声明此类型的变量,但类型的结构对用户不可用。实际类型是在包规范的末尾定义的,紧跟着保留字private的另一个用法。
因为这个类型是私有的,所以用户不能使用这个结构,即使他可以看到这个结构,就像你现在看到的那样。如果包编写器希望这样做,他实际上可以从列表中删除类型定义,并将结果列表提供给用户,但事实上,它是私有的,这就等于在物理上对用户隐藏了它。
由于类型是私有的,用户可以比较该类型的两个变量是否相等或不相等,并且可以将该类型的另一个变量或常量赋给同一类型的变量。对于这个私有类型的变量,除了在包规范本身中特别定义的那些操作之外,他没有其他操作可用。
这次别耍花招
因此,最后一个程序的第99行中的技巧是非法的,并将导致编译错误。如果用户需要对这个类型变量执行一些操作,他必须请求包编写器为他提供。包编写器能够处理包本身中的结构组件,并定义智能使用类型所需的任何新过程或函数。为了让用户将13添加到一个成员中,现在需要声明一个临时记录变量,将其与所有字段一起加载,并将类型 DATA_STRUCTURE的整个临时变量添加到该变量中。第106行和第107行对此进行了说明。很明显,这两个语句可以组合在一起,不需要名为Temp的变量,但这样做是为了清楚起见。
哪有这种型号的?
在第11行记录类型的部分声明之后,它可以在规范包的其余部分以及整个包体中使用。重要的是要记住,私有类型在这些区域中是可用的,就像它不是私有的一样。它只在使用程序方面以不同的方式处理。实际上,私有数据有两种不同的视图,一种是可供任何外部调用程序使用的局部视图,另一种是可供定义类的实现或主体使用的完整视图。实际上,在私有部分之前的规范中只有部分视图可用,在记录的完整定义之后,私有部分中可以使用完整视图。
程序的其余细节应该简单易懂,便于编译和执行。请注意,输出与上一个示例程序的输出相同。
一个新的PACKAGE
Example program ------> e_c21_p3.ada
软件包的作者,出于他自己神秘的原因,决定改变软件包使用不同的结构。名为e_c21_p3.ada的示例程序正是这样做的。与e_c21_p2.ada唯一不同的是定义结构的方法,在本例中使用了数组。因为用户被迫遵循规则,所以他的程序将不做任何修改地运行。查看是否可以使用在任一程序中定义的结构在主程序中找到在另一程序中无法完成的任何操作。根据Ada的设计,你不可能这么做。
请注意,包体的内部工作方式明显不同,以反映两个数据结构中的差异,但从外部看,您无法分辨差异。即使包发生了重大更改,但没有任何用户会看到差异,也不会出现不兼容问题。注意,这个示例程序中的主程序与上一个示例程序中的主程序相同。一定要编译并执行这个程序。
有限私有类型
Example program ------> e_c21_p4.ada
检查名为e_c21_p4.ada的程序以获取有限私有类型的示例。这个程序和上一个程序最大的区别是在第11行中增加了limited,它声明类型DATA_STRUCTURE 为 limited private.。这样,包编写器就可以更好地控制用户可以使用这种类型的变量做什么。除了包编写器以过程和函数的形式显式说明的操作外,用户不能对这种类型的数据执行任何操作。
如果用户要声明这种类型的两个变量,编译器将不允许他比较它们是否相等或不相等,也不能将一个变量赋给另一个变量。事实上,他甚至不能声明这种类型的变量并将其初始化为某个值,因为这需要一个隐含的赋值语句。他也不能声明这种类型的常量,因为这也需要赋值语句。包编写器提供了一个名为Assign_Struct的过程和一个名为Compare的函数,以便用户可以执行这些操作。从第95行开始的程序本身有点混乱,因为执行赋值需要过程调用。你肯定在问,“为什么我们要不辞辛劳地使用limited private类型?”
为什么使用有限类型?
可以想象,作为包编写者,由于项目的性质,您不希望让用户能够比较或分配所讨论类型的变量。您可能正在使用某种形式的数据加密系统或有限访问文件管理系统,并且希望确保所有用户都被完全锁定,无法访问某些文件。缺少比较和分配将使用户无法访问系统的较低级别。这是the limited type or the limited private type的一个可能用法,但是对它有更重要的需求。编译并执行这个程序,我们将讨论使用limited 类型的原因。
重载“=”运算符
equals运算符是为private 类型定义的,因此不能像我们在本教程中重载运算符那样重载它。 limited private类型没有可供使用的相等运算符,因此可以通过包编写器指定的任何方式重载它。例如,假设您使用的是本教程第16章中的动态字符串包,名为e_c16_p3.ada,您希望比较两个字符串是否相等。例如,如果字符串的长度为80个字符,但此时只有31个字符在使用,则相等运算符的重载可以提供一个只比较字符串的31个“active”字符的函数,从而在静态字符串声明中不包含其他字符。这将允许使用中缀运算符而不是函数调用进行比较。除了所有Ada中的limited private类型或limited类型外,任何类型都不允许重载“=”运算符。
Example program ------> e_c21_p5.ada
-- Chapter 21 - Program 5 -- Note: This program is based on the example program from chapter -- 18 named operover.ada, and is used to illustrate the different -- style required when using a limited type. package Shape is type BOX is limited record Length : INTEGER; Width : INTEGER; Height : INTEGER; end record; -- function Make_A_Box(In_Length, In_Width, In_Height : INTEGER) -- return BOX; procedure Make_A_Box(Result_Box : out BOX; In_Length, In_Width, In_Height : INTEGER); function "="(Left, Right : BOX) return BOOLEAN; function "+"(Left, Right : BOX) return BOX; procedure Print_Box(Input_Box : BOX); end Shape; with Ada.Text_IO, Ada.Integer_Text_IO; use Ada.Text_IO, Ada.Integer_Text_IO; package body Shape is procedure Make_A_Box(Result_Box : out BOX; In_Length, In_Width, In_Height : INTEGER) is begin Result_Box.Length := In_Length; Result_Box.Width := In_Width; Result_Box.Height := In_Height; end Make_A_Box; function "="(Left, Right : BOX) return BOOLEAN is begin if (Left.Length = Right.Length and Left.Width = Right.Width and Left.Height = Right.Height) then return TRUE; else return FALSE; end if; end "="; function "+"(Left, Right : BOX) return BOX is Temp_Box : BOX; begin Temp_Box.Length := Left.Length + Right.Length; Temp_Box.Width := Left.Width + Right.Width; Temp_Box.Height := Left.Height + Right.Height; return Temp_Box; end "+"; procedure Print_Box(Input_Box : BOX) is begin Put("Length = "); Put(Input_Box.Length, 3); Put(" Width = "); Put(Input_Box.Width, 3); Put(" Height = "); Put(Input_Box.Height, 3); New_Line; end Print_Box; end Shape; with Ada.Text_IO, Shape; use Ada.Text_IO; use type Shape.BOX; procedure OperOver is Small_Box : Shape.BOX; Big_Box : Shape.BOX; begin -- Small_Box := Shape.Make_A_Box(2, 3, 2); -- Big_Box := Shape.Make_A_Box(4, 5, 3); Shape.Make_A_Box(Small_Box, 2, 3, 2); Shape.Make_A_Box(Big_Box, 4, 5, 3); Shape.Print_Box(Small_Box); Shape.Print_Box(Big_Box); if Small_Box = Small_Box then Put_Line("The small box is the same size as itself."); else Put_Line("The small box is not itself."); end if; if Small_Box /= Big_Box then Put_Line("The boxes are different sizes."); else Put_Line("The boxes are the same size."); end if; end OperOver; -- Result of execution -- Length = 2 Width = 3 Height = 2 -- Length = 4 Width = 5 Height = 3 -- The small box is the same size as itself. -- The boxes are different sizes.
在名为e_uc21_up5.ada的示例程序中给出了一个非常有用的示例。记录定义为类型limited,这意味着它没有可用的赋值,它没有相等或不等式的比较运算符。这是为了明确定义我们自己的比较操作符,我们在第20行中做了。由于函数“=”返回布尔类型,因此它还为返回布尔值的不等式提供了默认比较,并且我们无法重新定义不等式运算符。如果我们已经定义了比较相等操作符以返回布尔以外的任何类型,我们将不会自动定义比较不等式,并且我们可以重载“/=”操作符返回任何我们希望它的类型。
本程序基于第18章中的e_c18_p6.ada,该功能使用第16和17行所示的Make_A_Box函数,但由于我们没有limited类型的分配能力,因此无法在此使用此函数。这意味着我们不能将结果分配给这个limited类型的变量。必须将函数转换为过程,这样我们就可以在调用程序中不赋值的情况下返回值。第88和89行被注释出来,因为它们必须重写为第91行和第92行,而这些行在调用程序中不使用赋值操作符。
这里我们得到的最大的一点是,我们不需要使用默认的比较操作符。我们不妨使用一些非常奇怪的手段作为平等的基础,使用limited 类型的手段将允许我们这样做。
您可能认为编写一个程序需要一个过程或函数调用来完成任何事情是非常低效的,但情况并非如此。如果编译器编写器做得很好,子程序调用可以非常高效,并且一个特殊目的的比较比设计用于处理所有情况的通用比较例程效率高得多。因为通用例程是为灵活性设计的,所以它可能不会非常有效地进行任何特定的比较。
请确保编译并执行此程序,以便验证它是否与最后两个示例程序执行相同的操作。
没有尸体的包裹
可以声明没有相应主体的包规范。如果您希望声明其他包和子程序可以使用的类型、变量和常量,但声明中没有包含可执行代码,则可以这样做。编程练习3将说明这一技术。没有相应的包规范,永远无法声明包主体。
编程练习
1.将一个名为Compare_Sum函数添加到PRIVATE1.ADA,该函数将一个结构的三个元素的和与第二个结构的三个元素的和进行比较,如果和相等,则返回TRUE,否则返回FALSE。在主程序中添加一些语句来执行这个新功能(Solution)
2.将相同的函数添加到PRIVATE2.ADA,并向主程序添加一些语句来测试它。.(Solution)
3.编写一个名为Stuff 的包,该包只包含包规范。包规范应使Pi(3.14159)和Two_Pi的值可用,以及DATE 记录类型。包含一个使用该包的简短程序。.(Solution)
---------------------------------------------------------------------------------------------------------------------------
原英文版出处:https://perso.telecom-paristech.fr/pautet/Ada95/a95list.htm
翻译(百度):博客园 一个默默的 *** 的人