2017-02-08 86 views
10

SYTUACJACzy naprawdę potrzebne są konstruktory rekordów Delphi?

uczę „Więcej kodowania w Delphi” Nick Hodges, a on stosuje TFraction rekord wyjaśnić przeciążanie operatorów. Pisałem ten sam rekord:

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; 

oczywiście musiałem utworzyć konstruktor, ponieważ w Q (wymiernych) Muszę mieć mianownik, który nie jest równy zeru.

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; 

PROBLEM

Ponieważ przeciążenia operatora zwrócić TFraction, mam zamiar zdefiniować operację tak:

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; 

Jak widać tutaj, jestem tworzenie tmp, która jest zwracana z funkcji.

Kiedy czytam książkę Marco Cantu, on używany innego podejścia:

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; 

Zrobiłem kilka testów, i widzę, że oba dają mi poprawny wynik, ale jest coś, że nie mogę zrozumieć. W pierwszym podejściu deklaruję tmp, a następnie wywołuję konstruktora, dzięki czemu mogę zwrócić TFraction. W drugim podejściu nie robię niczego, ponieważ rekordy mają automatyczny konstruktor. Dokumentacja w rzeczywistości, mówi, że:

Records są skonstruowane automatycznie, przy użyciu domyślnego no dwuargoomentowe konstruktora, ale zajęcia muszą być wyraźnie skonstruowana. Ponieważ rekordy mają domyślny konstruktor bezargumentowy, każdy zdefiniowany przez użytkownika konstruktor rekordów musi mieć jeden lub więcej parametrów.

Tutaj mam zdefiniowany przez użytkownika konstruktor rekordów. A więc:

  1. Czy wywołanie konstruktora na tmp pierwszego podejścia nie jest potrzebne? Jeśli chcę zadzwonić pod numer Reduce (co jest procedurą), muszę utworzyć zmienną. Czy urządzenie Result właśnie zwróci kopię tmp bez tworzenia?

  2. W drugim podejściu są Result.aNumerator i Result.aDenominator parametry automatycznego utworzonego konstruktora?

+0

W przypadku niepowiązanych notatek, zwykle normalną praktyką jest użycie 'F' jako przedrostka dla prywatnych pól w klasie oraz przedrostek' A' dla parametrów metody (których używasz). W Delphi wszystko jest w porządku, ale najwyraźniej w Łazarzu to się nie skomplikuje. –

+0

Ok, dziękuję, nie wiedziałem, używam a dla parametrów i zmiennych. Zmienię nawyk! –

+0

@Jerry Co masz na myśli. FPC nie wymusza reguł konwencji nazewnictwa. –

Odpowiedz

9

Rekordowy konstruktor nie jest niczym magicznym. To tylko metoda instancji, jak każda inna.Piszesz:

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

Ale można równie dobrze napisać to tak:

tmp.Create(...); 

Ja osobiście znaleźć ani być szczególnie przydatne, ponieważ jestem przyzwyczajony do konstruktora nazywając semantyki dla klas, które alokuje i domyślnej Inicjalizacja pamięć, a następnie wywołaj metodę konstruktora.

A szczególnie drugi wariant kratuje ze mną, ponieważ wygląda to jak klasyczny błąd, który początkujący programiści Delphi robią, gdy zaczynają i próbują stworzyć instancję klasy. Ten kod nie byłby dobry, gdyby klasa TFraction była klasą, ale na dowód jest w porządku.

Gdybym to ja pozbyłbym się konstruktora rekordów i zamiast tego użyłbym statycznej funkcji klasy, która zwróciła nowo wybite instancje twojego rekordu. Moją konwencją jest nazywanie takich rzeczy New. Ale to są sprawy osobistych preferencji.

Jeśli tak, że będzie ogłoszony tak:

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

byłoby realizowane tak:

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

Będziesz wtedy nazwać tak:

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

Ale jak powiedziałem, jest to kwestia preferencji. Jeśli lubisz konstruktorów rekordów, nie krępuj się z nimi.


Pytasz, czy możesz pominąć konstruktora. Jeśli chodzi o przydzielanie rekordu, tak, możesz go pominąć. Jeśli chodzi o uruchamianie kodu w konstruktorze, tylko Ty możesz to ustalić. Czy chcesz, aby ten kod był uruchamiany, czy nie?

Jeśli chcesz tego kodu do wykonania, ale nie chcą używać zmiennej tymczasowej, to można napisać kod jak poniżej:

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; 

Lub jeśli preferowane statyczną funkcję klasy miałoby to be:

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; 
+0

Preferuję sposób konstruktora, ponieważ wydaje mi się czystszy. Nick robi dokładnie to, co sugerujesz mi z funkcją klasy –

+0

Również nie wiedziałem o wyniku. Czy traktuje się ją jak zwykłą zmienną? Widzę, że możesz nazywać to Reduce! –

+3

Konstruktorzy IMO na rekordach wprowadzają w błąd (ogólnie). Nie ma sposobu, aby upewnić się, że kiedykolwiek zostanie użyty. Rekord to "wartość", a nie obiekt. –