Results 1 to 2 of 2

Thread: Basisuitleg over het maken van classes

  1. #1
    Fornicatorus Formicidae VideoRipper's Avatar
    Join Date
    Mar 2005
    Location
    Vicus Saltus Orientalem
    Posts
    5,708
    Vaak is het de beeldvorming die voor het grootste probleem zorgen; OOP (Object
    Oriented Programming) vraagt om een iets andere kijk en denkwijze dan traditioneel
    of proceduriëel programeren.

    Je moet een klasse (of object) zien als een losse blok logica (een "Black Box") die
    een bepaalde functionaliteit bevat en die je van buitenaf kunt aansturen zonder
    dat je hoeft te weten hoe het er van binnen aan toe gaat.

    Klassen geven je de mogelijkeid om functionele code te scheiden en om meerdere
    instanties van een klasse te hebben in je programma, zonder dat het een veelvoud
    aan geheugenruimte kost voor iedere instantie en om code overzichtelijk te houden.

    Bijkomend voordeel van een klasse is ook dat alle eventuele properties, netjes
    gegroepeerd zijn en duidelijk onderdeel uitmaken van de klasse en dat deze
    properties "Dom" kunnen zijn (gewone variabelen) of "Intelligent" (dat ze eerst
    nog door de klasse een controle of bewerking kunnen ondergaan, alvorens ze
    daadwerkelijk in de klasse worden opgeslagen/gebruikt) mbv een "Getter" en/of "Setter".

    Eventuele "Domme" properties kunnen in een later stadium "Intelligent" gemaakt
    worden, door ze van een "Getter" en/of "Setter" te voorzien, zonder dat je je
    hele programma overhoop hoeft te halen; het aanpassen van de klasse zelf
    is voldoende.

    Neem het volgende voorbeeld:
    Delphi Code:
    1. Procedure Berekenen1;
    2. Var
    3.   A1,           // Variabelen voor 1e waarden
    4.   A2,
    5.   A3,
    6.   A4,
    7.   B1,           // Variabelen voor 2e waarden
    8.   B2,
    9.   B3,
    10.   B4 : Single;
    11.   O  : Single;  // Optellen
    12.   A  : Single;  // Aftrekken
    13.   V  : Single;  // Vermenigvuldigen
    14.   D  : Single;  // Delen
    15. Begin
    16.   A1 := 10;
    17.   B1 := 20;
    18.   A2 := 30;
    19.   B2 := 40;
    20.   A3 := 50;
    21.   B3 := 60;
    22.   A4 := 70;
    23.   B4 := 80;
    24.  
    25.   O := B1 + A1;
    26.   A := B1 - A1;
    27.   V := B1 * A1;
    28.   D := B1 / A1;
    29.   ShowMessage('A = ' + FloatToStr(A1) + ', B = ' + FloatToStr(B1) + #13#10 +
    30.               'Optellen = ' + FloatToStr(O) + #13#10 +
    31.               'Aftrekken = ' + FloatToStr(A) + #13#10 +
    32.               'Vermenigvuldigen = ' + FloatToStr(V) + #13#10 +
    33.               'Delen = ' + FloatToStr(D));
    34.  
    35.   O := B2 + A2;
    36.   A := B2 - A2;
    37.   V := B2 * A2;
    38.   D := B2 / A2;
    39.   ShowMessage('A = ' + FloatToStr(A2) + ', B = ' + FloatToStr(B2) + #13#10 +
    40.               'Optellen = ' + FloatToStr(O) + #13#10 +
    41.               'Aftrekken = ' + FloatToStr(A) + #13#10 +
    42.               'Vermenigvuldigen = ' + FloatToStr(V) + #13#10 +
    43.               'Delen = ' + FloatToStr(D));
    44.  
    45.   O := B3 + A3;
    46.   A := B3 - A3;
    47.   V := B3 * A3;
    48.   D := B3 / A3;
    49.   ShowMessage('A = ' + FloatToStr(A3) + ', B = ' + FloatToStr(B3) + #13#10 +
    50.               'Optellen = ' + FloatToStr(O) + #13#10 +
    51.               'Aftrekken = ' + FloatToStr(A) + #13#10 +
    52.               'Vermenigvuldigen = ' + FloatToStr(V) + #13#10 +
    53.               'Delen = ' + FloatToStr(D));
    54.  
    55.   O := B4 + A4;
    56.   A := B4 - A4;
    57.   V := B4 * A4;
    58.   D := B4 / A4;
    59.   ShowMessage('A = ' + FloatToStr(A4) + ', B = ' + FloatToStr(B4) + #13#10 +
    60.               'Optellen = ' + FloatToStr(O) + #13#10 +
    61.               'Aftrekken = ' + FloatToStr(A) + #13#10 +
    62.               'Vermenigvuldigen = ' + FloatToStr(V) + #13#10 +
    63.               'Delen = ' + FloatToStr(D));
    64. End;
    Bovenstaande procedure neemt een 4-tal getallenparen en berekent vervolgens
    het opgetelde, afgetrokken, vermenigvuldigde en gedeelde resultaat en laat deze zien.

    Verder weinig noemenswaardige code, maar 1 ding valt al direct op: wat zijn die
    variabelen? Waar dienen ze voor?

    Met zoiets eenvoudigs als dit is dat makkelijk bij te houden, maar zodra een programma
    groeit wordt het steeds onoverzichtelijker en zie je door de bomen op een gegeven
    moment het bos niet meer.

    Uiteraard kun je de variabelen betere/duidelijkere namen geven, maar je kunt ze
    ook gewoon groeperen, zodat duidelijk te zien is welke variabelen er bij elkaar horen.

    Dit doen we met klassen!

    Een klasse moet je eerst declareren als een Type, zodat de Delphi-compiler hem
    correct kan klassificeren en registreren in z'n systeem.
    Dit declareren doen we in het "Interface"-gedeelte van onze code:
    Delphi Code:
    1. Unit BerekenUnit; // Naam van deze unit
    2.  
    3. Interface // Eerst gaan we het uitleggen aan buitenstaande code hoe deze
    4.           // unit (en zijn types) aangesproken dient te worden
    5. Uses
    6.   SysUtils;  // Hier zetten we de units neer die we nodig hebben voor onze eigen
    7.             // code (bijvoorbeeld de functie FloatToStr die in SysUtils zit)
    8. type
    9.   TMijnCalculator = Class(TObject)
    10.   Private
    11.     fValueA : Single;
    12.     fValueB : Single;
    13.   Protected
    14.     Function GetOptellen : Single;
    15.     Function GetAftrekken : Single;
    16.     Function GetVermenigvuldigen : Single;
    17.     Function GetDelen : Single;
    18.   Public
    19.     Constructor Create;
    20.     Property ValueA                   : Single Read fValueA Write fValueA;
    21.     Property ValueB                   : Single Read fValueB Write fValueB;
    22.     Property Optellen                 : Single Read GetOptellen;
    23.     Property Aftrekken                : Single Read GetAftrekken;
    24.     Property Vermenigvuldigen         : Single Read GetVermenigvuldigen;
    25.     Property Delen                    : Single Read GetDelen;
    26.   End;
    Allereerst geven we de klasse een naam, dit mag iedere willekeurge naam zijn,
    zolang het maar duidelijk aangeeft wat die klasse doet.
    Volgens de geldende Delphi naamgeving moet een type-declaratie met een hoofdletter
    "T" beginnen (zonder T ervoor doet de compiler ook niet moeilijk, maar we moeten
    ons wel een beetje aan de geaccepteerde regels houden natuurlijk).

    Zorg er overigens wel voor dat je niet een naam bedenkt die al bestaat, dit om
    verwarring te voorkomen met een reeds bestaande klasse!

    Oke, dus we noemen onze klasse "TMijnCalculator".
    Omdat onze klasse geen functionaliteit hoeft te bevatten die al in een andere klasse
    beschikbaar is kunnen we hem gewoon afleiden van de laagste basis-klasse die
    er bestaat: TObject.

    TObject is een klasse die verder weinig om het lijf heeft en maar een paar basis-methods
    (procedures en functies) heeft om het zaakje te laten werken.

    Het aangeven dat onze TMijnCalculator een afgeleide is van TObject doe je middels:
    Delphi Code:
    1. TMijnCalculator = Class(TObject)
    Let op: er staat geen punt-komma (; ) achter! Dit omdat de type-declaratie nog niet klaar is.

    Vervolgens gaan we een aantal variabelen toevoegen aan de klasse die niet beschikbaar
    zijn
    voor code buiten de klasse; deze zetten we dan ook in een "Private"-blok.
    Deze variabelen hebben we nodig voor de werking van onze klasse.

    Daarna declareren we een aantal functies (of procedures) die nodig zijn om de klasse
    zijn werk te laten doen, maar we willen niet dat code buiten onze klasse deze functies
    kunnen uitvoeren, dus plaatsen we ze in een "Protected"-blok.

    Als laatste zetten we dingen neer die code van buiten de klasse aan mogen spreken,
    zoals bepaalde openbaar toegankelijke functies, procedures en "Properties".
    Omdat ze voor code vanaf buiten toegankelijk moeten zijn, zetten we ze in een blok
    genaamd "Public".

    Properties kunnen eenvoudige (domme) variabelen zijn of in- en uit-gangen van
    waarden via een getter en/of setter.
    Onder properties worden ook eventuele events verstaan (welke eigenlijk gewoon
    pointers zijn naar een procedure van een bepaald type).

    Om het einde aan te geven van onze TMijnCalculator-klasse complementeren we
    de declaratie met "End;"

    In feite is de klasse-definitie voldoende voor een programmeur om met deze klasse
    te gaan werken; hij hoeft tenslotte niet te weten wat er in de klasse-code staat,
    zolang de klasse maar doet wat hij moet doen.

    Helaas moet de compiler wel eerst de werkende code maken, dus moeten we deze
    code ook implementeren...

    Dit doen we in het "Implementation"-gedeelte van onze code:
    Delphi Code:
    1. Implementation
    2.  
    3. Function TMijnCalculator.GetOptellen : Single;
    4. Begin
    5.   Result := fValueB + fValueA;
    6. End;
    7.  
    8. Function TMijnCalculator.GetAftrekken : Single;
    9. Begin
    10.   Result := fValueB - fValueA;
    11. End;
    12.  
    13. Function TMijnCalculator.GetVermenigVuldigen : Single;
    14. Begin
    15.   Result := fValueB * fValueA;
    16. End;
    17.  
    18. Function TMijnCalculator.GetDelen : Single;
    19. Begin
    20.   If (fValueA <> 0) Then
    21.     Result := fValueB / fValueA
    22.   Else
    23.     Result := 0;
    24. End;
    25.  
    26. Constructor TMijnCalculator.Create;
    27. Begin
    28.   Inherited Create;
    29.   fValueA := 0;
    30.   fValueB := 0;
    31. End;
    Met deze code maken we de klasse af en kan de compiler het resultaat naar
    machinetaal omzetten (compileren).
    Met de Constructor TMijnCalculator.Create kunnen we er voor zorgen dat bepaalde
    variabelen op een standaard waarde worden gezet, of zodat andere klassen die
    door jouw klasse worden gebruikt, ge-create kunnen worden.

    Een tegenhanger van Constructor bestaat ook: Destructor Tnaamvandeklasse.Destroy
    Als een klasse wordt vernietigd, wordt deze destructor aangeroepen zodat je
    bepaalde acties kunt uitvoeren zoals het destroyen van gebruikte klassen.

    Overigens zien we bij de TMijnCalculator.GetDelen-functie nog een voordeel bij
    het maken van klassen: alvorens het resultaat gegeven wordt kun je controleren
    of de berekening wel mogelijk is (delen door 0 kan namelijk niet).

    Bij proceduriele code zou je hier midden in de code zelf op moeten controleren en
    handelen, terwijl in een klasse altijd een geldig antwoord komt (of 0).

    Om onze unit zelf af te sluiten, moeten we nog het einde aangeven met:
    Delphi Code:
    1. End.

    Vervolgens kunnen we de klasse gaan gebruiken met bijvoorbeeld deze code:
    Delphi Code:
    1. Uses
    2.   BerekenUnit;
    3.  
    4. Procedure Berekenen2;
    5. Var
    6.   C1,
    7.   C2,
    8.   C3,
    9.   C4 : TMijnCalculator;
    10. begin
    11.   C1 := TMijnCalculator.Create;
    12.   Try
    13.     C1.ValueA := 10;
    14.     C1.ValueB := 20;
    15.  
    16.     C2 := TMijnCalculator.Create;
    17.     Try
    18.       C2.ValueA := 30;
    19.       C2.ValueB := 40;
    20.  
    21.       C3 := TMijnCalculator.Create;
    22.       Try
    23.         C3.ValueA := 50;
    24.         C3.ValueB := 60;
    25.  
    26.         C4 := TMijnCalculator.Create;
    27.         Try
    28.           C4.ValueA := 70;
    29.           C4.ValueB := 80;
    30.  
    31.           ShowMessage('A = ' + FloatToStr(C1.ValueA) + ', B = ' + FloatToStr(C1.ValueB) + #13#10 +
    32.                       'Optellen = ' + FloatToStr(C1.Optellen) + #13#10 +
    33.                       'Aftrekken = ' + FloatToStr(C1.Aftrekken) + #13#10 +
    34.                       'Vermenigvuldigen = ' + FloatToStr(C1.Vermenigvuldigen) + #13#10 +
    35.                       'Delen = ' + FloatToStr(C1.Delen));
    36.  
    37.           ShowMessage('A = ' + FloatToStr(C2.ValueA) + ', B = ' + FloatToStr(C2.ValueB) + #13#10 +
    38.                       'Optellen = ' + FloatToStr(C2.Optellen) + #13#10 +
    39.                       'Aftrekken = ' + FloatToStr(C2.Aftrekken) + #13#10 +
    40.                       'Vermenigvuldigen = ' + FloatToStr(C2.Vermenigvuldigen) + #13#10 +
    41.                       'Delen = ' + FloatToStr(C2.Delen));
    42.  
    43.           ShowMessage('A = ' + FloatToStr(C3.ValueA) + ', B = ' + FloatToStr(C3.ValueB) + #13#10 +
    44.                       'Optellen = ' + FloatToStr(C3.Optellen) + #13#10 +
    45.                       'Aftrekken = ' + FloatToStr(C3.Aftrekken) + #13#10 +
    46.                       'Vermenigvuldigen = ' + FloatToStr(C3.Vermenigvuldigen) + #13#10 +
    47.                       'Delen = ' + FloatToStr(C3.Delen));
    48.  
    49.           ShowMessage('A = ' + FloatToStr(C4.ValueA) + ', B = ' + FloatToStr(C4.ValueB) + #13#10 +
    50.                       'Optellen = ' + FloatToStr(C4.Optellen) + #13#10 +
    51.                       'Aftrekken = ' + FloatToStr(C4.Aftrekken) + #13#10 +
    52.                       'Vermenigvuldigen = ' + FloatToStr(C4.Vermenigvuldigen) + #13#10 +
    53.                       'Delen = ' + FloatToStr(C4.Delen));
    54.  
    55.         Finally
    56.           C4.Free;
    57.         End;
    58.       Finally
    59.         C3.Free;
    60.       End;
    61.     Finally
    62.       C2.Free;
    63.     End;
    64.   Finally
    65.     C1.Free;
    66.   End;
    67. end;
    Om een klasse te kunnen gebruiken moeten we, naast een variabele waarmee
    we hem kunnen refereren, eerst creëren; als we er mee klaar zijn moeten we
    hem ook weer vrijgeven.
    Bij het Createn wordt er geheugen vrijgemaakt voor een nieuwe instantie van
    onze klasse en deze moet weer worden vrijgegeven als we ermee klaar zijn anders
    krijg je geheugenlekken (en het is gewoon slordig).

    Vandaar dat je gecreëerde klassen in een Try...Finally...End-constructie moet
    zetten, om ervoor te zorgen dat je klasse altijd wordt opgeruimd, ook als er tussentijds
    iets fout mocht gaan (een exceptie bijvoorbeeld).

    Code tussen Finally...End wordt altijd uitgevoerd, ook als er een exceptie optreed.

    We pakken er even 1 gecreëerde klasse uit (de anderen zijn zo goed als identiek):
    Delphi Code:
    1. Begin
    2.   C1 := TMijnCalculator.Create;
    3.   Try
    4.     C1.ValueA := 10;
    5.     C1.ValueB := 20;
    6.     ShowMessage('A = ' + FloatToStr(C1.ValueA) + ', B = ' + FloatToStr(C1.ValueB) + #13#10 +
    7.                 'Optellen = ' + FloatToStr(C1.Optellen) + #13#10 +
    8.                 'Aftrekken = ' + FloatToStr(C1.Aftrekken) + #13#10 +
    9.                 'Vermenigvuldigen = ' + FloatToStr(C1.Vermenigvuldigen) + #13#10 +
    10.                 'Delen = ' + FloatToStr(C1.Delen));
    11.   Finally
    12.     C1.Free;
    13.   End;
    14. End;
    Regel 2 in onze code zorgt ervoor dat instantie C1 van onze klasse gecreërd wordt;
    in de klasse zelf wordt nu eerst intern fValueA en fValueB op 0 gezet.
    Daarna lopen we het Try-blok in waar we vanuit onze code aan ValueA en ValueB
    een waarde wordt toegekend (respectivelijk 10 en 20).
    In de klasse verwijzen deze properties direct naar de interne variabelen fValueA en
    fValueB, dus hier gebeurt verder weinig spannends...

    In de ShowMessage op regel 6 wordt vervolgens eerst een complete string samengesteld
    voor de ShowMessage zelf uit alle losse strings en functies die er als parameter inzitten.

    Even "Optellen" als voorbeeld nemende (voor de andere properties is de werking
    gelijk): omdat we een string willen hebben (voor de ShowMessage) en de Optellen-
    property een single (= float) als uitgang heeft moeten we gebruik maken van
    de functie FloatToStr() welke in SysUtils zit.

    Deze FloatToStr vraagt aan onze klasse-instantie (C1.Optellen) wat de waarde is.
    Onze klasse kijkt in zijn lijst en ziet dat het geen domme property is, maar gebruik
    maar van een Getter, dus roept hij intern de functie "TMijnCalculator.GetOptellen" aan
    welke de som van fValueA en fValueB teruggeeft als resultaat.

    Zo worden alle uitkomsten opgevraagd uit de klasse, omgezet naar strings en als
    parameter aan ShowMessage gevoerd, welke vervolgens het resultaat laat zien.

    Uiteraard is bovenstaande een beetje onzinnig en totale overkill, maar het dient
    alleen maar ter illustratie.

    Maar... kunnen we misschien onze klasse toch nog uitbreiden met handige functionaliteit?

    Jazeker! Kijk maar eens naar wat onze eigen code moet doen in de ShowMessage
    om er een geldige string uit te halen...

    We kunnen heel eenvoudig extra functies of properties toevoegen aan onze klasse
    die in plaats van de standaard Single-waarde ook direct een string-versie ter
    beschikking stelt:
    Delphi Code:
    1. // In onze TMijnCalculator-klasse
    2.     Function GetOptellenAsString : String;
    3.     Function GetAftrekkenAsString : String;
    4.     Function GetVermenigvuldigenAsString : String;
    5.     Function GetDelenAsString : String;
    6. //
    7.  
    8. Implementation
    9.  
    10. Function TMijnCalculator.GetOptellenAsString : String;
    11. Begin
    12.   Result := FloatToStr(GetOptellen);
    13. End;
    14.  
    15. Function TMijnCalculator.GetAftrekkenAsString : String;
    16. Begin
    17.   Result := FloatToStr(GetAftrekken);
    18. End;
    19.  
    20. Function TMijnCalculator.GetVermenigvuldigenAsString : String;
    21. Begin
    22.   Result := FloatToStr(GetVermenigvuldigen);
    23. End;
    24.  
    25. Function TMijnCalculator.GetDelenAsString : String;
    26. Begin
    27.   Result := FloatToStr(GetDelen);
    28. End;
    Deze extra functies kunnen we gewoon toevoegen aan het Public-gedeelte van
    onze klasse, zodat ze direct publiekelijk toegankelijk zijn vanaf buiten, maar we kunnen
    ook de extra stap zetten door ze te implementeren als Getters van properties
    (en de functies zelf dan in het Private-gedeelte te plaatsen).

    De klasse komt er dan zo uit te zien:
    Delphi Code:
    1. type
    2.   TMijnCalculator = Class(TObject)
    3.   Private
    4.     fValueA : Single;
    5.     fValueB : Single;
    6.   Protected
    7.     Function GetOptellen : Single;
    8.     Function GetOptellenAsString : String;
    9.     Function GetAftrekken : Single;
    10.     Function GetAftrekkenAsString : String;
    11.     Function GetVermenigvuldigen : Single;
    12.     Function GetVermenigvuldigenAsString : String;
    13.     Function GetDelen : Single;
    14.     Function GetDelenAsString : String;
    15.   Public
    16.     Constructor Create;
    17.     Property ValueA                   : Single Read fValueA Write fValueA;
    18.     Property ValueB                   : Single Read fValueB Write fValueB;
    19.     Property Optellen                 : Single Read GetOptellen;
    20.     Property OptellenAsString         : String Read GetOptellenAsString;
    21.     Property Aftrekken                : Single Read GetAftrekken;
    22.     Property AftrekkenAsString        : String Read GetAftrekkenAsString;
    23.     Property Vermenigvuldigen         : Single Read GetVermenigvuldigen;
    24.     Property VermenigvuldigenAsString : String Read GetVermenigvuldigenAsString;
    25.     Property Delen                    : Single Read GetDelen;
    26.     Property DelenAsString            : String Read GetDelenAsString;
    27.   End;
    Met deze versie van onze klasse kunnen we direct de berekende waarde als
    string opvragen zoals hieronder:
    Delphi Code:
    1. //
    2.     ShowMessage('A = ' + FloatToStr(C1.ValueA) + ', B = ' + FloatToStr(C1.ValueB) + #13#10 +
    3.                 'Optellen = ' + C1.OptellenAsString + #13#10 +
    4.                 'Aftrekken = ' + C1.AftrekkenAsString + #13#10 +
    5.                 'Vermenigvuldigen = ' + C1.VermenigvuldigenAsString + #13#10 +
    6.                 'Delen = ' + C1.DelenAsString);
    7. //
    Dan nog een ding over over-erving (inheritence)...

    Zoals verteld hebben we onze TMijnCalculator-klasse overgeëfd van TObject en TObject heeft
    van zichzelf ook al een aantal methods in zich zitten voor de basis-functionaliteit.

    Probeer voor de grap eens deze code:
    Delphi Code:
    1. Procedure Test;
    2. Var
    3.   C : TMijnCalculator;
    4. begin
    5.   C := TMijnCalculator.Create;
    6.   Try
    7.     ShowMessage(C.ClassName);
    8.   Finally
    9.     Free;
    10.   End;
    11. End;
    Als je deze procedure uitvoert, krijg je een message te zien die zegt: "TMijnCalculator"
    Maar we hebben helemaal niet de property of functie TMijnCalculator.ClassName in onze
    klasse gedeclareert!

    Dat klopt... deze is onderdeel van (en wordt geïmplementeert in) TObject en wordt
    in die klasse (de voorouder van onze klasse dus) uitgevoerd, maar wij hebben daar
    wel de beschikking over in onze overgeërfde klassen)!

    Ik hoop dat mijn uitleg enigszins te begrijpen (en compleet genoeg) is om te gaan
    experimenteren met eigen klassen.

    Nog een ding wil ik wel even kwijt: Klassen zijn niet de heilige graal en de oplossing
    tot alle voorkomende problemen; soms is het beter/handige/korter om gewoon
    procedurieel te werken, dan om er een complete klasse van te maken.

    Overweeg dus goed of het inpaken in een klasse wel verantwoord kan worden

    Succes!

    Peter.
    TMemoryLeak.Create(Nil);

  2. #2
    Mooie uitleg! Even losgeweekt uit "Classes, nog te ingewikkeld of?".
    1+1=b

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •