Действительно ли нужны конструкторы записей Delphi?

СИТУАЦИЯ

Я изучаю «Больше кодирования в Delphi» Ника Ходжеса, и он использует запись TFraction для объяснения перегрузки оператора. Я сам написал эту запись:

type
  TFraction = record
  strict private
    aNumerator: integer;
    aDenominator: integer;
    function GCD(a, b: integer): integer;
  public
    constructor Create(aNumerator: integer; aDenominator: integer);
    procedure Reduce;
    class operator Add(fraction1, fraction2: TFraction): TFraction;
    class operator Subtract(fraction1, fraction2: TFraction): TFraction;
    //... implicit, explicit, multiply...
    property Numerator: integer read aNumerator;
    property Denominator: integer read aDenominator;
  end;

Конечно, мне пришлось создать конструктор, потому что в Q (рациональные числа) у меня должен быть знаменатель, не равный нулю.

constructor TFraction.Create(aNumerator, aDenominator: integer);
begin
  if (aDenominator = 0) then
  begin
    raise Exception.Create('Denominator cannot be zero in rationals!');
  end;

  if ( (aNumerator < 0) or (aDenominator < 0) ) then
  begin
    Self.aNumerator := -aNumerator;
    Self.aDenominator := -aDenominator;
  end
  else
  begin
    Self.aNumerator := aNumerator;
    Self.aDenominator := aDenominator;
  end;
end;

ПРОБЛЕМА

Поскольку перегрузки оператора возвращают TFraction, я собираюсь определить такую ​​операцию:

class operator TFraction.Add(fraction1, fraction2: TFraction): TFraction;
var
  tmp: TFraction;
begin
  //simple algorithm of the sum
  tmp := TFraction.Create(fraction1.Numerator*fraction2.Denominator+fraction1.Denominator*fraction2.Numerator, fraction1.Denominator*fraction2.Denominator);
  tmp.Reduce;

  //return the result
  Result := tmp;
end;

Как вы можете видеть здесь, я создаю tmp, который возвращается функцией.

Когда я читал книгу Марко Канту, он использовал другой подход:

class operator TFraction.Add(fraction1, fraction2: TFraction): TFraction;
begin
  Result.aNumerator := (fraction1.Numerator*fraction2.Denominator+fraction1.Denominator*fraction2.Numerator);
  Result.aDenominator := fraction1.Denominator*fraction2.Denominator;
end;

Я провел несколько тестов и вижу, что оба дают правильный результат, но есть кое-что, чего я не могу понять. В первом подходе я объявляю tmp, а затем вызываю конструктор, чтобы я мог вернуть TFraction. Во втором подходе я ничего не создаю, потому что у записей есть автоматический конструктор. В документации, по сути, сказано, что:

Записи создаются автоматически с использованием конструктора без аргументов по умолчанию, но классы должны быть созданы явно. Поскольку записи имеют конструктор без аргументов по умолчанию, любой определяемый пользователем конструктор записи должен иметь один или несколько параметров.

Здесь у меня есть определяемый пользователем конструктор записи. Так:

  1. Не нужен ли вызов конструктора для tmp первого подхода? Если я хочу вызвать Reduce (который является процедурой), мне нужно создать переменную. Result просто возвращает копию tmp, ничего не создавая?

  2. При втором подходе Result.aNumerator и Result.aDenominator являются параметрами автоматически созданного конструктора?


person Alberto Miola    schedule 08.02.2017    source источник
comment
Кстати, обычная практика заключается в использовании F в качестве префикса для частных полей в классе и префикса A для параметров метода (который вы действительно используете). В Delphi это нормально, но, видимо, в Lazarus это не компилируется.   -  person Jerry Dodge    schedule 08.02.2017
comment
Хорошо, спасибо, я не знал, я использую для параметров и переменных. Я изменю свою привычку!   -  person Alberto Miola    schedule 08.02.2017
comment
@Jerry Что ты имеешь в виду. FPC не применяет правила соглашения об именах.   -  person David Heffernan    schedule 08.02.2017
comment
@David stackoverflow.com/questions/42073767/   -  person Jerry Dodge    schedule 08.02.2017
comment
@jerry Я не уверен, что верю в это. Я подозреваю, что комментарий Реми содержит правильную информацию.   -  person David Heffernan    schedule 08.02.2017


Ответы (1)


В конструкторе записей нет ничего волшебного. Это просто метод экземпляра, как и любой другой. Ты пишешь:

tmp := TFraction.Create(...);

Но вы также можете написать это так:

tmp.Create(...);

Я лично считаю, что ни один из них не особенно полезен, потому что я привык к семантике вызова конструктора для классов, которые выделяют и инициализируют память по умолчанию, а затем вызывают метод конструктора.

И особенно мне нравится второй вариант, потому что это выглядит как классическая ошибка, которую допускают начинающие программисты Delphi, когда начинают и пытаются создать экземпляр класса. Этот код был бы бесполезен, если бы TFraction был классом, но для записи это нормально.

Если бы это был я, я бы избавился от конструктора записи и вместо этого использовал бы функцию статического класса, которая возвращала бы только что созданный экземпляр вашего типа записи. Я условился называть такие вещи New. Но это вопрос личных предпочтений.

Если бы вы это сделали, это было бы объявлено так:

class function New(aNumerator, aDenominator: Integer): TFraction; static;

Это будет реализовано так:

class function TFraction.New(aNumerator, aDenominator: Integer): TFraction;
begin
  Result.aNumerator := ...;
  Result.aDenominator := ...;
end;

Тогда вы бы назвали это так:

frac := TFraction.New(num, denom);

Но, как я уже сказал, это вопрос предпочтений. Если вам нравятся конструкторы пластинок, смело используйте их.


Вы спрашиваете, можно ли пропустить конструктор. Что касается распределения записи, да, вы можете ее пропустить. Что касается запуска кода в конструкторе, это можете определить только вы. Вы хотите, чтобы этот код выполнялся или нет?

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

class operator TFraction.Add(fraction1, fraction2: TFraction): TFraction;
begin
  Result.Create(
    fraction1.Numerator*fraction2.Denominator + fraction1.Denominator*fraction2.Numerator,
    fraction1.Denominator*fraction2.Denominator
  );
  Result.Reduce;
end;

Или, если вы предпочитаете функцию статического класса, это будет:

class operator TFraction.Add(fraction1, fraction2: TFraction): TFraction;
begin
  Result := TFraction.New(
    fraction1.Numerator*fraction2.Denominator + fraction1.Denominator*fraction2.Numerator,
    fraction1.Denominator*fraction2.Denominator
  );
  Result.Reduce;
end;
person David Heffernan    schedule 08.02.2017
comment
Я предпочитаю конструктор, потому что мне он кажется чище. Ник делает именно то, что вы мне предлагаете, с функцией класса - person Alberto Miola; 08.02.2017
comment
Также я не знал о результате. Считается ли это нормальной переменной? Я вижу, что вы можете вызвать это сокращение! - person Alberto Miola; 08.02.2017
comment
Конструкторы IMO в записях вводят в заблуждение (в общем). Нет никакого способа убедиться, что он когда-либо будет использован. Запись - это ценность, а не объект. - person Etsitpab Nioliv; 08.02.2017
comment
Я немного помог Нику с этой главой. Кажется, вы знаете, что такое Result, потому что используете его в своем коде. Когда вы ему присваиваете. Это просто неявный параметр var, который содержит возвращаемое значение. - person David Heffernan; 08.02.2017
comment
Что касается использования конструкторов записей, не жалуйтесь на то, что вы запутались, и вызывайте obj.Create, где obj - ссылка на класс !! Я помню, как обсуждал это с Ником, когда он писал главу. - person David Heffernan; 08.02.2017
comment
Читая предисловие, я только что увидел, что некий Дэвид Хеффернан предложил Нику использовать пример с дробями, вы его знаете? ;) - person Alberto Miola; 08.02.2017
comment
Я углубляюсь в суть оператора класса в записи и конструкторе, и если у меня есть сомнения, я задам правильный вопрос. - person Alberto Miola; 08.02.2017
comment
Здесь нет правильного или неправильного. Все сводится к предпочтениям. Не нужно слишком зацикливаться на этом. - person David Heffernan; 08.02.2017
comment
@DavidHeffernan, если я хочу сохранить атрибуты записи как частные, тогда использование конструктора для передачи значений атрибутов - единственный способ, потому что с помощью метода класса вы можете получить доступ только к общедоступным атрибутам. Правильно? - person Delmo; 12.11.2018
comment
@delmo Нет. На самом деле код в моем ответе демонстрирует метод класса, обращающийся к строгим частным полям. - person David Heffernan; 12.11.2018
comment
@DavidHeffernan поля aNumerator и aDenominator объявлены OP строго закрытыми, и вы не изменили это в своем ответе. Строгие частные поля недоступны из метода класса, поэтому в этом конкретном случае лучше использовать конструктор. Правильно? - person Delmo; 13.11.2018
comment
@Delmo Как я уже сказал, код в моем ответе демонстрирует метод класса, обращающийся к строгим частным полям. Ясно, что у вас заблуждение. Компиляция кода в моем ответе может помочь вам преодолеть это. - person David Heffernan; 13.11.2018
comment
tmp.Create и tmp: = TFraction.Create (...) разные ...... tmp.Create напрямую работает с экземпляром tmp. tmp: = TFraction.Create сначала создаст дополнительное ВРЕМЕННОЕ значение, а затем скопирует и присвоит это значение tmp - person Wuping Xin; 19.07.2020