在Ada的生成器模式

问题描述 投票:3回答:4

我仍然是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;
ada
4个回答
8
投票

如上所述,支持问题的一种方法是使用不需要值的参数的默认值:

function Create (First_Name     : String;
                 Middle_Name    : String := "";
                 Last_Name      : String;
                 Date_Of_Birth  : String;
                 Place_Of_Birth : String := "")
                return Person;

它接受你所有的例子。


3
投票

所以是的,这是可能的。

我强烈建议你不要采用这种方法。它有非常糟糕的性能问题,更不用说难以维护了。话虽如此,事实上它是可能的(并且应该以任何语言进行调度)。它是通过扩展到一个使用中间类型的流畅模式来实现的,以保持主要类型只读。因为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;

这是命令行输出

enter image description here

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


1
投票

我相信Java具有Builder模式,因为它不支持默认参数。 Java中的Builder模式为那些不想使用函数重载的人创建了一种解决方法。 Ada确实有默认参数,因此Ada方式来解决这个问题(不使用重载)就是使用默认参数,正如Simon Wright所建议的那样。

这种方法的好处是,这为您提供了编译时检查,而使用Builder模式,显然它是一个运行时检查。使用Simon建议的Create函数,例如,无法创建没有名字的Person。

所以在Ada中,我认为没有必要实现Builder模式,因为语法中内置了更好的机制。但是,如果确实想要实现构建器模式,我的方法是使用Ada流功能来构建属性对象流,这些属性对象可以传递到读取流的构建过程,并构建对象。这基本上就是Java Build模式正在做的事情。然而,这会将错误检查放回到运行时,而不是像在Java中那样在编译时。


-1
投票

从我的观点来看,这需要编译器在编译时知道构建器对象的内容,所以这是不可能的,但我可能是错的。

但是,一个解决方案(实际上不是构建器模式)可能是声明中间类型,例如

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时,后者已正确初始化。

© www.soinside.com 2019 - 2024. All rights reserved.