Ada 中的构建器模式
Builder pattern in Ada
我对 Ada 还是个新手,对 Ada 中处理面向对象的方式不是很精通。 :(
我想知道是否可以在 Ada 中实现类似 builder 的模式?这种模式在 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;
Builder 类似的模式(不知道这在 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;
乍一看,这需要编译器在编译时知道构建器对象的内容,所以这是不可能的,但我可能错了。
不过,一个解决方案可能不是真正的构建器模式,可能是声明中间类型,例如
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
请记住我没有编译这样的代码:)
另一方面,通过编写前置条件和 post- 条件,您可以使用 Spark 检查此 属性,然后在调用 Build 时证明这一点 在您的 Builder 对象上,后者已正确初始化。
一种 Ada 支持所述问题的方法是为不需要值的参数使用默认值:
function Create (First_Name : String;
Middle_Name : String := "";
Last_Name : String;
Date_Of_Birth : String;
Place_Of_Birth : String := "")
return Person;
接受你所有的例子。
我相信 Java 有 Builder 模式,因为它不支持默认参数。 Java 中的 Builder 模式为那些不想使用函数重载的人创建了一个变通方法。
Ada 确实有默认参数,因此解决此需求的 Ada 方法(不使用重载)是使用默认参数,正如 Simon Wright 所建议的那样。
这种方法的一个好处是,这为您提供了编译时检查,而使用构建器模式,显然它是 运行 时间检查。例如,使用 Simon 建议的 Create 函数,不能创建没有名字的 Person。
所以在 Ada 中,我认为不需要实现构建器模式,因为语法中内置了更好的机制。然而,如果确实想要实现构建器模式,我的方法是使用 Ada 流功能来构建属性对象流,这些对象可以传递到读取流并构建对象的构建过程中。这基本上就是 Java 构建模式正在做的事情。然而,这会将错误检查放回 运行 时间,而不是像在 Java.
中那样在编译时进行
所以是的,这是可能的。
我强烈建议您不要采用这种方法。它有非常糟糕的性能问题,更不用说更难维护了。话虽如此,这实际上是可能的(并且应该使用任何语言进行调度)。它是通过对流畅模式的扩展来实现的,该模式使用中间类型来保持主要类型只读。因为 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
支持变异但不是通过 属性 模式,而是通过 returns 每次调用新实例的流畅模式。然后可以将这些修改链接起来作为流畅应用的结果。
我对 Ada 还是个新手,对 Ada 中处理面向对象的方式不是很精通。 :(
我想知道是否可以在 Ada 中实现类似 builder 的模式?这种模式在 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;
Builder 类似的模式(不知道这在 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;
乍一看,这需要编译器在编译时知道构建器对象的内容,所以这是不可能的,但我可能错了。
不过,一个解决方案可能不是真正的构建器模式,可能是声明中间类型,例如
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
请记住我没有编译这样的代码:)
另一方面,通过编写前置条件和 post- 条件,您可以使用 Spark 检查此 属性,然后在调用 Build 时证明这一点 在您的 Builder 对象上,后者已正确初始化。
一种 Ada 支持所述问题的方法是为不需要值的参数使用默认值:
function Create (First_Name : String;
Middle_Name : String := "";
Last_Name : String;
Date_Of_Birth : String;
Place_Of_Birth : String := "")
return Person;
接受你所有的例子。
我相信 Java 有 Builder 模式,因为它不支持默认参数。 Java 中的 Builder 模式为那些不想使用函数重载的人创建了一个变通方法。 Ada 确实有默认参数,因此解决此需求的 Ada 方法(不使用重载)是使用默认参数,正如 Simon Wright 所建议的那样。
这种方法的一个好处是,这为您提供了编译时检查,而使用构建器模式,显然它是 运行 时间检查。例如,使用 Simon 建议的 Create 函数,不能创建没有名字的 Person。
所以在 Ada 中,我认为不需要实现构建器模式,因为语法中内置了更好的机制。然而,如果确实想要实现构建器模式,我的方法是使用 Ada 流功能来构建属性对象流,这些对象可以传递到读取流并构建对象的构建过程中。这基本上就是 Java 构建模式正在做的事情。然而,这会将错误检查放回 运行 时间,而不是像在 Java.
中那样在编译时进行所以是的,这是可能的。
我强烈建议您不要采用这种方法。它有非常糟糕的性能问题,更不用说更难维护了。话虽如此,这实际上是可能的(并且应该使用任何语言进行调度)。它是通过对流畅模式的扩展来实现的,该模式使用中间类型来保持主要类型只读。因为 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
支持变异但不是通过 属性 模式,而是通过 returns 每次调用新实例的流畅模式。然后可以将这些修改链接起来作为流畅应用的结果。