Шаблон строителя в Аде - PullRequest
3 голосов
/ 22 марта 2019

Я все еще новичок в Аде и не очень хорошо разбираюсь в способах ориентации объектов в Аде. (

Я хотел бы знать, возможно ли реализовать builder подобный шаблон в 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;

Builder образец (не знаю, возможно ли это в Аде):

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;

Первый вопрос: как этот пример может быть реализован в Аде?

Функция Build может проверять (во время выполнения), все ли необходимые атрибуты инициализированы соответствующими функциями.

Второй вопрос: может ли эта проверка делегироваться (магическим образом) компилятору, чтобы следующий пример не компилировался?

declare
    Person : Person;
begin
    -- Last_Name function not called
    Person := Person.Builder.First_Name("John")
                            .Date_Of_Birth("1990-02-27")
                            .Build();
end;

Ответы [ 4 ]

8 голосов
/ 22 марта 2019

Один из способов поддержки проблемы Ada, как указано, заключается в использовании значений по умолчанию для параметров, значения которых не требуются:

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

, который принимает все ваши примеры.

3 голосов
/ 23 марта 2019

Так что да, это возможно.

Я настоятельно рекомендую вам не придерживаться этого подхода.У него очень плохие проблемы с производительностью, не говоря уже о том, что его сложнее поддерживать.Это, как говорится, на самом деле возможно (и должно быть на любом языке с отправкой).Это достигается расширением беглого шаблона, в котором используется промежуточный тип, чтобы основной тип оставался доступным только для чтения.Поскольку в 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

Теперьпозволь мне объяснить.Ваш основной тип, конечно, Person, с Person_Builder, действующим как изменчивая форма этого.Builder преобразует из Person в Person_Builder и Build преобразует из Person_Builder обратно в Person.Person поддерживает только доступ только для чтения к полям через шаблон свойств.Точно так же Person_Builder поддерживает мутацию, но не через шаблон свойства, а через беглый шаблон, который возвращает новый экземпляр при каждом вызове.Эти изменения могут быть затем объединены в цепочку в результате свободного применения.

1 голос
/ 22 марта 2019

Я считаю, что в Java есть шаблон Builder, поскольку он не поддерживает параметры со значениями по умолчанию.Шаблон Builder в Java создает обходной путь для тех, кто не хочет использовать перегрузку функций.У Ada есть параметры по умолчанию, поэтому Ada способ удовлетворить эту потребность (без использования перегрузки) - использовать параметры по умолчанию, как было предложено Саймоном Райтом.

Преимущество этого подхода заключается в том, что это дает вампроверка времени компиляции, тогда как при использовании шаблона Builder, по-видимому, это проверка во время выполнения.Используя функцию «Создать», предложенную Саймоном, нельзя создать человека, у которого нет имени, например.

Так что в Ada я бы сказал, что нет необходимости реализовывать Builderшаблон, так как лучшие механизмы встроены в синтаксис.Однако, если бы кто-то захотел реализовать шаблон построителя, мой подход состоял бы в том, чтобы использовать возможности потоковой передачи Ada для создания потока объектов атрибутов, которые можно передать в процедуру Build, которая считывает поток и создает объект.Это по сути то, что делает шаблон Java Build.Это, однако, возвращает проверку ошибок обратно во время выполнения, а не во время компиляции, как это происходит в Java.

0 голосов
/ 22 марта 2019

С моей точки зрения, на первый взгляд, это потребовало бы, чтобы компилятор знал содержимое вашего объекта компоновщика во время компиляции, и поэтому это невозможно, но я могу ошибаться.

Одно решение, хотя,который на самом деле не является шаблоном компоновщика, может заключаться в объявлении промежуточных типов, таких как

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, а затем доказать, что при вызове Build для вашего Builder этот последний объект корректно инициализируется.

...