我仍然是Ada的新手,并且不太熟悉Ada处理对象方向的方式。 :(
我想知道是否有可能在Ada中实现类似模式的构建器?这种模式在Java编程语言中很常见。
一个简单的例子:假设我想模拟一个人对象。一个人具有以下属性:
我可以实现四个(重载)Create
函数来涵盖所有可能的组合:
declare
Person_1 : Person;
Person_2 : Person;
Person_3 : Person;
Person_4 : Person;
begin
Person_1 := Create(First_Name => "John",
Last_Name => "Doe",
Date_Of_Birth => "1990-02-27");
Person_2 := Create(First_Name => "John",
Middle_Name => "Michael",
Last_Name => "Doe",
Date_Of_Birth => "1990-02-27");
Person_3 := Create(First_Name => "John",
Last_Name => "Doe",
Date_Of_Birth => "1990-02-27",
Place_Of_Birth => "New York");
Person_4 := Create(First_Name => "John",
Middle_Name => "Michael",
Last_Name => "Doe",
Date_Of_Birth => "1990-02-27",
Place_Of_Birth => "New York");
end;
像生成器模式(不知道这是否可能在Ada中):
declare
Person_1 : Person;
Person_2 : Person;
Person_3 : Person;
Person_4 : Person;
begin
Person_1 := Person.Builder.First_Name("John")
.Last_Name("Doe")
.Date_Of_Birth("1990-02-27")
.Build();
Person_2 := Person.Builder.First_Name("John")
.Middle_Name("Michael")
.Last_Name("Doe")
.Date_Of_Birth("1990-02-27")
.Build();
Person_3 := Person.Builder.First_Name("John")
.Last_Name("Doe")
.Date_Of_Birth("1990-02-27")
.Place_Of_Birth("New York")
.Build();
Person_4 := Person.Builder.First_Name("John")
.Middle_Name("Michael")
.Last_Name("Doe")
.Date_Of_Birth("1990-02-27")
.Place_Of_Birth("New York")
.Build();
end;
第一个问题:如何在Ada中实现此示例?
Build
函数可以(在运行时)检查所有必需属性是否由所属函数初始化。
第二个问题:这个检查是否可以(以神奇的方式)委托给编译器,所以下面的例子不能编译?
declare
Person : Person;
begin
-- Last_Name function not called
Person := Person.Builder.First_Name("John")
.Date_Of_Birth("1990-02-27")
.Build();
end;
如上所述,支持问题的一种方法是使用不需要值的参数的默认值:
function Create (First_Name : String;
Middle_Name : String := "";
Last_Name : String;
Date_Of_Birth : String;
Place_Of_Birth : String := "")
return Person;
它接受你所有的例子。
所以是的,这是可能的。
我强烈建议你不要采用这种方法。它有非常糟糕的性能问题,更不用说难以维护了。话虽如此,事实上它是可能的(并且应该以任何语言进行调度)。它是通过扩展到一个使用中间类型的流畅模式来实现的,以保持主要类型只读。因为Ada没有只读字段,所以您还需要使用属性模式以只读方式公开字段。
这是规格
with Ada.Strings.Unbounded;
use Ada.Strings.Unbounded;
package Persons is
type Person is tagged private;
function First_Name(Self : in Person) return String;
function Middle_Name(Self : in Person) return String;
function Last_Name(Self : in Person) return String;
function Date_of_Birth(Self : in Person) return String;
function Place_of_Birth(Self : in Person) return String;
type Person_Builder is tagged private;
function Builder return Person_Builder;
function First_Name(Self : in Person_Builder; Value : in String) return Person_Builder;
function Middle_Name(Self : in Person_Builder; Value : in String) return Person_Builder;
function Last_Name(Self : in Person_Builder; Value : in String) return Person_Builder;
function Date_of_Birth(Self : in Person_Builder; Value : in String) return Person_Builder;
function Place_of_Birth(Self : in Person_Builder; Value : in String) return Person_Builder;
function Build(Source : in Person_Builder'Class) return Person;
private
type Person is tagged record
First_Name : Unbounded_String;
Middle_Name : Unbounded_String;
Last_Name : Unbounded_String;
Date_of_Birth: Unbounded_String;
Place_of_Birth: Unbounded_String;
end record;
type Person_Builder is tagged record
First_Name : Unbounded_String;
Middle_Name : Unbounded_String;
Last_Name : Unbounded_String;
Date_of_Birth: Unbounded_String;
Place_of_Birth: Unbounded_String;
end record;
end Persons;
和身体
package body Persons is
function First_Name(Self : in Person) return String is (To_String(Self.First_Name));
function Middle_Name(Self : in Person) return String is (To_String(Self.Middle_Name));
function Last_Name(Self : in Person) return String is (To_String(Self.Last_Name));
function Date_of_Birth(Self : in Person) return String is (To_String(Self.Date_of_Birth));
function Place_of_Birth(Self : in Person) return String is (To_String(Self.Place_of_Birth));
function Builder return Person_Builder is
begin
return Person_Builder'(To_Unbounded_String(""), To_Unbounded_String(""), To_Unbounded_String(""), To_Unbounded_String(""), To_Unbounded_String(""));
end Builder;
function First_Name(Self : in Person_Builder; Value : in String) return Person_Builder is
begin
return Person_Builder'(To_Unbounded_String(Value), Self.Middle_Name, Self.Last_Name, Self.Date_of_Birth, Self.Place_of_Birth);
end First_Name;
function Middle_Name(Self : in Person_Builder; Value : in String) return Person_Builder is
begin
return Person_Builder'(Self.First_Name, To_Unbounded_String(Value), Self.Last_Name, Self.Date_of_Birth, Self.Place_of_Birth);
end Middle_Name;
function Last_Name(Self : in Person_Builder; Value : in String) return Person_Builder is
begin
return Person_Builder'(Self.First_Name, Self.Middle_Name, To_Unbounded_String(Value), Self.Date_of_Birth, Self.Place_of_Birth);
end Last_Name;
function Date_of_Birth(Self : in Person_Builder; Value : in String) return Person_Builder is
begin
return Person_Builder'(Self.First_Name, Self.Middle_Name, Self.Last_Name, To_Unbounded_String(Value), Self.Place_of_Birth);
end Date_of_Birth;
function Place_of_Birth(Self : in Person_Builder; Value : in String) return Person_Builder is
begin
return Person_Builder'(Self.First_Name, Self.Middle_Name, Self.Last_Name, Self.Date_of_Birth, To_Unbounded_String(Value));
end Place_of_Birth;
function Build(Source : in Person_Builder'Class) return Person is
begin
return Person'(Source.First_Name, Source.Middle_Name, Source.Last_Name, Source.Date_of_Birth, Source.Place_of_Birth);
end Build;
end Persons;
然后使用该包的示例程序
with Ada.Text_IO, Persons;
use Ada.Text_IO, Persons;
procedure Proof is
P : Person;
begin
P := Builder
.First_Name("Bob")
.Last_Name("Saget")
.Place_of_Birth("Philadelphia, Pennsylvania")
.Build;
Put_Line("Hello, my name is " & P.First_Name & " " & P.Last_Name & " and I am from " & P.Place_of_Birth);
Put_Line("Middle Name: " & P.Middle_Name);
Put_Line("Date of Birth: " & P.Date_of_Birth);
end Proof;
这是命令行输出
现在让我解释一下。你的主要类型当然是Person
,Person_Builder
充当它的可变形式。 Builder
从Person
转换为Person_Builder
,Build
从Person_Builder
转换回Person
。 Person
仅支持通过属性模式对字段进行只读访问。类似地,Person_Builder
支持变异但不支持属性模式,而是通过一个流畅的模式来返回每个调用的新实例。然后可以通过流畅的应用来链接这些修改。
我相信Java具有Builder模式,因为它不支持默认参数。 Java中的Builder模式为那些不想使用函数重载的人创建了一种解决方法。 Ada确实有默认参数,因此Ada方式来解决这个问题(不使用重载)就是使用默认参数,正如Simon Wright所建议的那样。
这种方法的好处是,这为您提供了编译时检查,而使用Builder模式,显然它是一个运行时检查。使用Simon建议的Create函数,例如,无法创建没有名字的Person。
所以在Ada中,我认为没有必要实现Builder模式,因为语法中内置了更好的机制。但是,如果确实想要实现构建器模式,我的方法是使用Ada流功能来构建属性对象流,这些属性对象可以传递到读取流的构建过程,并构建对象。这基本上就是Java Build模式正在做的事情。然而,这会将错误检查放回到运行时,而不是像在Java中那样在编译时。
从我的观点来看,这需要编译器在编译时知道构建器对象的内容,所以这是不可能的,但我可能是错的。
但是,一个解决方案(实际上不是构建器模式)可能是声明中间类型,例如
type Person_with_name is tagged record
First_name : String(1..50);
end record;
type Person_with_last_name is new Person_With_First_Name with
record
Last_Name : String(1..50);
end record;
type Person_with_last_name is new Person_With_Birth with
record
Date_Of_Birth : Date;
end record;
然后在Builder对象中有每个函数返回这些类型的函数
function LastName(with_first : Person_With_First_Name, last_name : String(1..50)) return Person_With_Last_Name;
function Date_Of_Birth(with_last : Person_With_Last_Name, date_Of_Birth : Date) return Person_With_Birth;
等等......但这有点难看:D
请记住,我没有编译这样的代码:)
另一方面,通过编写前置条件和后置条件,您可以使用Spark检查此属性,然后证明,在Builder对象上调用Build时,后者已正确初始化。