ObjectenHome

Object Oriënted Programming met Delphi

1. Inleiding

Jarenlang heb ik object oriëntatie toegepast met behulp van Delphi zonder dat ik goed op de hoogte was van de ins- en outs van Object georiënteerd programmeren.
Op deze manier programmeren met Delphi gaat prima, echter meer dan het toepassen van standaard of aangekochte componenten is er niet te beleven.
Inmiddels zijn mijn ogen geopend en wil ik middels dit paper mijn ervaringen en opgedane kennis delen.
Na doorspitten van diverse literatuur heb ik veel geoefend, en zijn er zowaar eigen componenten onstaan.

Dit paper behandelt : De theorie van Object Oriënted Programming.

In de volgende papers ga ik verder met het exploren van de Delphi VCL en het ontwikkelen van eigen componenten.
In de behandeling in dit paper is er vanuit gegaan dat enige Delphi kennis en ervaring is opgedaan zoals hierboven omschreven

2. Objecten en klasse

2.1 OOP

Tot voor de invoer van OOP in Pascal, volgens mij was dat versie 5.5, was het gegevenstype record het type wat het meest weg heeft van de hedendaagse objecten.
Een record is een gegevenstype dat een aantal velden bevat.
Die velden zijn de attributen van het record en geven een eigenschap aan het record.
Een object is in feite ook een record, maar bevat naast attributen (velden) ook methoden. Methoden zijn eigenlijk niks anders dan procedures en functie’s.
Vaak wordt ook de term klasse (Class) gebruikt in de samenhang met objecten. Het is echter zo dat een klasse geen object is.

2.2 Van klasse naar object

Een klasse is een gedefinieerd gegevenstype en heeft status en gedrag.
Een object is een instantie van een klasse.
Je moet dit eigenlijk net zo zien als bij de declaratie van een variabele : Var I : Integer; Waarbij I de instantie van het gegevenstype integer is.

Populair gezegd de Klasse is het recept, en het object is het gerecht.

Binnen Delphi begint een Class altijd met de hoofdletter T. (Dit is een conventie).

var auto : TVoertuig;

Auto is hier het object.
Daar Delphi volgens het object referentie model werkt is Auto hier in werkelijkheid een pointer naar het object. Dit is wel essentieel binnen Object Pascal.

2.3 Wat maakt een programmeertaal object georiënteerd ?

De vier belangrijkste eigenschappen van OOP - programmeertaal zijn :

Deze vier eigenschappen worden straks verder besproken.

 

2.4 De class definitie

We hebben nu een hele berg theorie verwerkt maar willen nu wel eens zien hoe een class er uitziet.
Een veel voorkomende is :

type
  TForm1 = class (Tform)

{Lijst met objecten en methode waarvan klasse eigenaar is.}

     private

   {Private declarations}

    public

    {public declarations}

end;

In de lijst met objecten en methode staan in dit geval (het is een form) bijvoorbeeld de declaratie’s van op
het form geplaatste componenten (buttons etc.).

2.5 Verdere uitdieping van OOP eigenschappen

In deze paragraaf gaan we dieper in op de eerder besproken hoofdeigenschappen van een OOP taal.

2.5.1 Abstraction

Is de mogelijkheid die een OOP taal biedt om de "echte"wereld te modeleren.
In het kort komt het er op neer dat de programma’s die wij schrijven een model bezitten van iets wat in de werkelijke wereld gebeurt.

2.5.2 Encapsulation

Het inkapselen van informatie (ook wel information hiding genoemd) is een belangrijke OOP eigenschap.
Zoals te zien is in de class definitie kunnen methoden en eigenschappen als volgt gedeclareerd worden :

Wat is nu het voordeel van de inkapseling ?
Als het private gedeelte van een klasse wordt gewijzigd dan heeft dit geen invloed op programma’s die
de klasse gebruiken. Ma.w. er hoeft geen hercompilatie plaats te vinden, het programma is toch niet op
de hoogte van alles wat zich binnen het private gedeelte afspeelt.

Scoping van variabelen (Geldigheid van variabelen)

Het doorgeven van variabelen van de ene unit naar de andere unit kan op verschillende manieren geschieden :

             Voorbeeld parameter overbrenging :

type
  TForm1 = class (Tform)
     {Lijst met objecten en methode waarvan klasse eigenaar is.}
   private
      {Private declarations}
      MijnTeller : Integer;
    public
     {public declarations}
     procedure SetMijnTeller ( I : Integer);
    function GetMijnTeller : Integer;
end;

procedure TForm1.SetMijnTeller ( I : Integer );
begin
    MijnTeller := I;
end;

function TForm1.GetMijnTeller : Integer;
begin
    Result := MijnTeller;
end;

Tip : Vergeet niet de class completion functie te gebruiken ! (Control-shift-C)

Een nog betere manier is om e.e.a via property’s te realiseren, maar hierover in een volgend paper meer
later meer.

2.5.3 De sleutel self
Voor we het over inheritance (overerving) gaan hebben gaan we eerst dieper op methoden in.
Methoden hebben, in tegenstelling tot procedures, een impliciete parameter, ofwel een referentie naar
het huidige object.
Binnen de methode wordt dit gerealiseerd via de sleutel self.
Self.MijnTeller :=2;

Let op : We gebruiken in dit opzicht de sleutel self nooit.

Verder wordt de sleutel self gebruikt bij het aanmaken van componenten tijdens de uitvoer van het
programma.
Hier moet dan de eigenaar van het object bekend gemaakt worden, en dit doe je d.m.v. van self.

Dynamisch componenten maken :

Stel in object TForm1 wordt een form gemaakt als volgt:

a:=TForm.Create(self);
a.show;

self verwijst hier naar het object waarvoor de methode wordt uitgevoerd.(TForm1 dus)
En dit is de owner.

Bij button moet ook een parent bekend worden gemaakt.
De button moet immers weten waar hij  zichzelf moet tekenen.

b : Tbutton;
b := TButton.Create(self);
b.parent := self;

De functie create is de zogenaamde constructor van het object en wordt aangeroepen om geheugen
aan het object toe te wijzen. Tevens kan de constructor gebruikt worden om  het object te initialiseren.

2.5.4 Overload –methoden en constructors

Methoden en constructors kunnen ‘overload’ worden. Dit houdt in dat een bepaalde methode
meerdere malen gedeclareerd mag worden binnen een ubit mits de sleutel Overload;
achter de betreffende methodes geplaatst wordt en de methodes verschillende parameters hebben.
De compiler bepaalt a.d.h. van de parameters welke methode aangeroepen wordt.

De volgende declaraties zijn dan mogelijk :

Constructor Create; Overload;
Constructor Create ( Teller : Integer); Overload;

2.5.5 Enheritance (overerving)

Enheritance is een belangrijke OOP eigenschap. Het staat het overerven van klassen toe.
Dit wordt ook wel ‘subclassen’ genoemd.
In Delphi wordt meteen bij de start van een nieuw project aan overerving gedaan :

TForm1 = class (TForm)

Het nieuwe form TForm1 overerft alle eigenschappen en methode van TForm.

Compabiliteit van typen

Hier volgt een voorbeeld van overerving :

TMotorVoertuig = class;
TMotorFiets = Class (TMotorVoertuig)

TMotorFiets overerf hier alle methoden en eigenschappen van TMotorVoertuig.
Als gevolg hiervan is de volgende declaratie toegestaan :
MijnMotorVoertuig := MijnMotorFiets;
Andersom is echter fout, een motorfiets kan geen motorvoertuig zijn.

Je kan dus altijd het object uit een nakomelingsklasse gebruiken als een object uit een voorouderklasse wordt verwacht.

2.5.6 Polymorfisme

Met polymorfisme begeven we ons op het gebied van geavanceerde OOP technieken.
Polymorfisme staat in principe ‘Late binding’ toe, waarmee we bedoelen dat het feitelijke adres
van de aan te roepen methode pas bepaald wordt tijdens de uitvoer van het programma.
Dit in tegenstelling tot ‘normale’ pascal procedures en functies, die middels ‘static binding’ vooraf door de
compiler een geheugenplaats krijgen toegewezen.

MijnMotorVoertuig uit het bovenstaande voorbeeld kan tijdens de uitvoering van het type TMotorVoertuig zijn, maar ook van het TMotorFiets.

type
   TMotorVoerTuig = class;
    Public
        Function Starten : Boolean; Virtual;
     end;

type
  TMotorFiets = class (TMotorVoertuig);
   Public
        function Starten : Boolean; Override;
  end;

 MijnVoertuig kan tijdens de uitvoering, zoals al eerder gezegd, van het type TMotorVoertuig of Tmotorfiets zijn.
 De sleutel virtual staat toe dat wanneer MijnMotorVoertuig van het type TMotorFiets is dat dan de functie starten
 van TMotorFiets wordt uitgevoerd.
 Hieruit blijkt dat de aanroep van MijnVoertuig.Starten compatible is met alle toekomstige subklassen van TMotorVoertuig.

 Het herroepen, herdefiniëren en herintroduceren van methoden

 Onder het herroepen van code wordt verstaan dat virtual gedeclareerde methode in de hoofdklasse die middels override in de subklasse
 wordt herroepen worden aangevuld met extra code.

 MijnClass = Class;
 Procedure DoeHet; Virtual;

 Mijnsubclass = Class (MijnClass);
 Procedure DoeHet; Override;

 Herroepen middels :

 Procedure MijnSubClass.DoeHet;
 Begin
      // De nieuwe code
      Inherited DoeHet; //Deze functie roept Procedure MijnClass.Doehet aan.
 end;

 Een methode die static is (dus niet virtual) kan je herdefiniëren door dezelfde methode toe te voegen aan de subklasse zonder verdere specificaties.
 Methodes die in de basis klasse virtual zijn kunnen herroepen worden middels overloading (verschillende parameters)
 echter de compiler zal dan een warning genereren :  Method {methode } hides virtual method of base type
 Om deze foutmelding te voorkomen moet de sleutel Reintroduce gebruikt worden.

 Procedure MijnSubclass.Doehet; reintroduce; overload;

Even nog op een rij :

 

2.5.7 Runtime type informatie

De compatibiliteitsregels in object pascal voor nakomelingsklassen zorgen ervoor dat nakomelingsklassen kunnen worden gebruikt
als voorouderklassen worden verwacht.
(Het omgekeerde is zoals we gezien hebben niet mogelijk)
Middels RTTI kunnen klassen getoets worden op hun bestaan. E.e.a. middels de operatoren is en as.

If MijnVoertuig is TMotorFiets then..........

(MijnVoertuig as TMotorFiets).Start; ( Is feitelijk een TypeCast)

Deze expressie’s werken als booleans en zijn true or false.

 

Tot zover theorie OOP.

arrows.gif (215 bytes)Top