Objecten
Naast het aanpassen en uitbreiden van bestaande objecten, kunnen we ook zelf nieuwe objecten maken, dat doen we door het bericht nieuw te sturen. Stel dat we bijvoorbeeld voor een facturatiesysteem een factuurobject willen hebben dat een opeenvolgende factuurnummering afdwingt, dan hebben we allereerst een factuurobject nodig. Omdat elk object afstamt van een ander object en uiteindelijk van het moederobject van alle objecten genaamd Object, moeten we altijd kiezen op welk van die andere objecten we ons nieuwe object gaan baseren. Ons nieuwe object overerft alle functionaliteiten van het object waarop het is gebaseerd (waarnaar we dus het nieuwbericht hebben gestuurd). In dit voorbeeld willen we graag een vrij neutraal object zonder al te veel overerfde functionaliteiten, de keuze is dan makkelijk, in dat geval baseren we ons nieuwe object op Object, het moederobject zelf. We kunnen ons facturatiesysteem in de dop dan zo schrijven:
>>factuur := Object nieuw.
factuur bij: ['begin'] doen: {
eigen nummer := 0.
}.
factuur bij: ['nummer'] doen: {
eigen nummer optellen: 1.
<- eigen nummer kopieer ruw.
}.
We gebruiken dit object als volgt:
factuur begin.
Uit schrijf: factuur nummer, stop.
Uit schrijf: factuur nummer, stop.
Uitvoer:
1
2
Het huidige factuurnummer is opgeslagen in het object, vandaar dat we er een sleutelpictogram (eigen) voor zetten. We noemen dit een eigenschap, daar komen we zo op terug.
Sommige bedrijven gebruiken liever factuurnummers waarin het jaartal verwerkt is. We kunnen dan een nieuw factuurobject maken dat gebaseerd is op ons eerdere factuurobject maar dat de gebruiker ervan de mogelijkheid biedt een jaartal op te geven:
>> jaartalfactuur := factuur nieuw.
jaartalfactuur bij: ['begin:'] doen: { :jaargang
eigen nummer := jaargang.
}.
Dit jaartalfactuurobject gebruiken we dan zo:
jaartalfactuur begin: 202000.
Uit schrijf: jaartalfactuur nummer, stop.
Uit schrijf: jaartalfactuur nummer, stop.
Resultaat:
202001
202002
Overerving
U kunt een hiërarchie van objecten maken. Bijvoorbeeld:
>> Dier := Object nieuw.
>> Hond := Dier nieuw.
>> Poedel := Hond nieuw.
In dit geval overerft Hond al het gedrag van Dier en het Dier-object op zijn beurt overerft weer al het gedrag van Object, het moederobject van alle objecten.
Overscrhijving
In het volgende voorbeeld maken we een nieuw soort reeks: Combinatie, waarbij elk element gegarandeerd uniek is. We hergebruiken de functies van de Reeks dankzij overerving:
>> Combinatie :=
Reeks nieuw.
Combinatie bij: ['toevoegen:'] doen: { :element
(zelf zoek: element) nee: {
zelf toevoegen: element.
}.
}.
>> kleurcombinatie := Combinatie nieuw.
kleurcombinatie
toevoegen: ['rood'],
toevoegen: ['groen'],
toevoegen: ['blauw'],
toevoegen: ['rood'].
Uit schrijf: kleurcombinatie, stop.
In dit geval wordt de tweede rood niet opgenomen in de reeks:
Reeks ← ['rood'] ; ['groen'] ; ['blauw']
Soms willen we het gedrag van een object overschrijven. Bijvoorbeeld bij het optellen van getallen, als we rekening willen houden met eenheden. In het volgende voorbeeld maken we een object genaamd Afmeting. Dit object geeft een getal terug dat bij de optelling rekening houdt met de enheid van het op te tellen getal, zo kijkt het Getal-object of het om meters of centimeters gaat. De programmacode zou er zo uit kunnen zien:
>> Afmeting := Object nieuw.
Afmeting bij: ['is:'] doen: { :getal
getal bij: ['+'] doen: { :getal
>> eenheid := getal kwalificering.
>> factor := 1.
eenheid
geval: ['centimeter'] doen: { factor := 0,01.
}.
>> antwoord := zelf + (getal * factor).
antwoord meter.
}.
<- antwoord.
<- getal.
}.
We kunnen dit afmetingsgetal als volgt gebruiken:
>> plank := Afmeting is: 6 meter.
>> rand := Afmeting is: 50 centimeter.
Uit schrijf: plank + rand, stop.
Het resultaat:
6,5 meter
In het bovenstaande voorbeeld overschrijven we het gedrag van het plusteken. Maar uiteindelijk moeten we natuurlijk nog wel de uiteindelijke optelling doen, dat gebeurt op deze regel:
>> antwoord := zelf + (getal * factor).
Hoe weet XOScript nu dat het + teken hier verwijst naar de originele logica van het optellen? Een andere interpretatie zou namelijk kunnen zijn dat XOScript weer opnieuw hetzelfde bericht verstuurt naar hetzelfde object en dat we in een eindeloze lus terechtkomen. Dat is natuurlijk niet de bedoeling! Op het moment dat u een bericht verstuurt naar een object waardoor dezelfde code zou worden uitgevoerd, weet XOScript dat u het achterliggende, overschreven bericht bedoeld. Uw programma wordt dus altijd automatisch tegen deze vorm van eindeloze lussen beveiligd. Wilt u daarentegen wel dezelfde taak uitvoeren vanuit de huidige taak, dan moet u eerst het bericht recursief sturen naar het object waarnaar u hetzelfde bericht wilt sturen, in dit geval zou u dan in een oneindige lus terechtkomen.
Recursie
Stel dat u bijvoorbeeld van een getal de faculteit wilt berekenen, in dat geval voegt u het bericht faculteit toe aan Getal:
Getal bij: ['faculteit'] doen: {
>> antwoord := 1.
(zelf > 0) ja: {
>> dit-getal := zelf.
>> volgende-getal := dit-getal - 1.
antwoord := (dit-getal * volgende-getal recursief faculteit).
}.
<- antwoord.
}.
Hiervoor hebben we recursie nodig. De taak die gekoppeld is aan het bericht faculteit moet namelijk opnieuw worden uitgevoerd binnen die taak. Dus we willen de faculteitstaak aanroepen vanuit de faculteitstaak zelf. Standaard steekt XOScript daar een stokje voor, omdat u het gevaar loopt om in een oneindige lus verzeild te raken. Om die reden moeten we het bericht vooraf laten gaan door het woordje recursief. Hiermee informeren we XOScript van het feit dat het echt onze bedoeling is om dezelfde taak opnieuw uit te laten voeren en we geen vergissing hebben gemaakt. De volgende lus toont de faculteiten voor de getallen 1-10:
{ :x Uit schrijf: x faculteit, stop. } * 10.
1
2
6
24
120
720
5.040
40.320
362.880
3.628.800
Zonder het woordje recursief zou alleen de eerste vermenigvuldiging plaatsvinden.
Klasse-objecten
Een veelvoorkomend probleem is dat we een object willen maken dat van begin af aan in een bepaalde toestand verkeert. Stel bijvoorbeeld dat we een object Rechthoek willen maken waarmee we omtrek en oppervlakte kunnen berekenen. We zouden dit als volgt kunnen schrijven:
>> Rechthoek := Object nieuw.
Rechthoek bij: ['oppervlak'] doen: {
<- (eigen lengte * eigen breedte).
}.
Voorwaarde is natuurlijk wel dat we een lengte en breedte instellen, hiervoor zouden we de berichten lengte: en breedte: kunnen toevoegen.
Rechthoek bij: ['lengte:'] doen: { :lengte
eigen lengte := lengte.
}.
Rechthoek bij: ['breedte:'] doen: { :breedte
eigen breedte := breedte.
}.
We kunnen onze rechthoek vervolgens gebruiken:
>> rechthoek := Rechthoek nieuw lengte: 2, breedte: 3.
Het probleem is echter dat als we vergeten om een lengte en breedte op te geven, we een foutmelding krijgen:
>> rechthoek := Rechthoek nieuw.
Uit schrijf: rechthoek oppervlak.
Onafgehandelde fout opgetreden.
Sleutel niet gevonden: lengte
#3 lengte (testje.ctr: 5)
#2 oppervlak (testje.ctr: 18)
#1 schrijf: (testje.ctr: 18)
Om dit te voorkomen zouden we eigenlijk liever willen dat een rechthoek altijd een lengte en breedte heeft, bijvoorbeeld 0. In dat geval moeten we dus het nieuw bericht overschrijven.
Rechthoek bij: ['nieuw'] doen: {
>> rechthoek := zelf nieuw.
rechthoek lengte: 0.
rechthoek breedte: 0.
<- rechthoek.
}.
Op regel 2 ziet u dat we de echte nieuw weer aanroepen. We komen hier niet in een oneindige lus terecht doordat XOScript ons hiertegen beschermt. Het achterliggende mechanisme hiervan hebben we eerder besproken in hoofdstuk 4.4. Als we nu een nieuwe rechthoek maken, krijgt deze van begin af aan de afmetingen 0 bij 0, op die manier kan er geen fout optreden bij het berekenen van de oppervlakte:
>> rechthoek := Rechthoek nieuw.
Uit schrijf: rechthoek oppervlak.
0
>> rechthoek := Rechthoek nieuw lengte: 2, breedte: 3.
Uit schrijf: rechthoek oppervlak.
6