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;

这是命令行输出

现在让我解释一下。您的主要类型当然是 PersonPerson_Builder 作为它的可变形式。 BuilderPerson 转换为 Person_BuilderBuildPerson_Builder 转换回 PersonPerson 仅支持通过 属性 模式对字段进行只读访问。同样,Person_Builder 支持变异但不是通过 属性 模式,而是通过 returns 每次调用新实例的流畅模式。然后可以将这些修改链接起来作为流畅应用的结果。