
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 functies.
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 :
Het modeleren in de werkelijke wereld.
Het inkapselen van data en methoden. Ook wel information hiding genoemd.
De mogelijkheid om data (eigenschappen) en methoden te overerven uit een hogere klasse.
Is de mogelijkheid tot "late binding" tijdens de uitvoer van het programma.
M.a.w. de aan te roepen methode (of eigenlijk het adres hiervan ) wordt pas vastgesteld
tijdens de uitvoering.
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 declaraties 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 programmas 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 :
Velden en methoden zijn niet toegankelijk buiten de unit.
Velden en methoden zijn vrij toegankelijk.
Velden en methoden zijn alleen toegankelijk door de huidige klassen en subklassen.
Wat is nu het voordeel van de inkapseling ?
Als het private gedeelte van een klasse wordt gewijzigd dan heeft dit geen invloed op
programmas 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 :
Deze methode is niet erg raadzaam daar de variabele voor het hele programma geldt en
als het ware
tijdens de uitvoer van het programma zweeft.
Dus declareer de variabele in het private gedeelte van het object. (Vaak is dat het
form)
En lees- en schrijf deze middels public gedeclareerde methodes.
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 propertys 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 expressies werken als booleans en zijn true or false.
Tot zover theorie OOP.