Een Smalltalk voorbeeld

  
24 maart 2009

Hieronder wordt een voorbeeld uitwerking van het ontwikkelen van een applicatie in Smalltalk gegeven. De opdracht om een applicatie voor het spelletje Galgje te bouwen komt voort uit onze werkgroep Smalltalk die door Rob en mij afgelopen maand gegeven is voor een groep enthousiaste deelnemers. Het artikel is dus een uitwerking van deze opgave, maar kan ook als tutorial voor beginnende Smalltalk ontwikkelaars gebruikt worden. Met dat doel heb ik het hier online gezet.

In dit voorbeeld willen we een applicatie voor het spelletje Galgje (Hangman) ontwikkelen in VisualWorks Smalltalk. Voor de mensen die het niet zo goed kennen. Galgje is een spelletje waarbij een speler een woord in gedachte neemt en voor elke letter in het woord een punt of streepje op papier zet. De andere speler probeert dan dit woord te achterhalen door steeds een letter te raden. Als de letter in het woord voorkomt vult de eerste speler de letters op de betreffende posities in. Als de letter niet in het woord voorkomt tekent de eerste spelen een deel van een galg op het papier. Het spelleltje houdt op als alle letters geraden zijn of de galg compleet is.

Package en namespace

Voordat we beginnen hebben we eerst een package en een namespace nodig. In VisualWorks maken we een package aan door in de System Browser rechts te clicken in het package venster. In het menu kiezen we ‘new package…’ zoals getoond in het screenshot hieronder.

Nieuw Package aanmaken in System Browser

Nieuw Package aanmaken in System Browser

Een package fungeert als een deployment unit en zorgt ervoor dat we onze applicatie als eenheid kunnen behandelen voor taken als archivering, versie beheer en deployen. Een namespace bepaald de scope van classes.

Nieuw Package Window

Nieuw Package Window

Er verschijnt een nieuw window waar de naam van het nieuwe package kan worden op gegeven. In een package naam kan in principe elke willekeurige character gebruikt worden. Spaties, hyphens en andere speciale characters zijn geen probleem. Binnen VisualWorks is het de conventie om subpackages te prefixen met het framework of applicatie waar het bijhoort gevolgd door een hyphen. Als onze applicatie uit meerdere packages zou bestaan zou het bijvoorbeeld uit ‘Galgje-Domain’ en ‘Galgje-Interface’ kunnen bestaan. Voor een eenvoudige applicatie als de onze kunnen we echter volstaan met de naam ‘Galgje’.

ss3-system-browser-new-namespace

Een nieuwe namespace aanmaken in de system browser

Nu we een package hebben kunnen we een namespace toevoegen. Dit voorkomt conflicten met classes die dezelfde naam hebben. Je kunt je misschien voorstellen dat meerdere applicaties een persoon class bezitten, maar dat deze toch anders is voor een salaris systeem, dan voor een online winkel.

Het aanmaken van een nieuwe namespace kan door te clicken met de rechter muisknop in het venster naast onze packages. Dit is een venster waar normaal onze classes getoond worden, maar omdat we nog geen classes hebben voor onze applicatie is het venster leeg. In het menu dat verschijnt keizen we de ‘new’ optie en daarna de ‘namespace…’ optie in het submenu.

New Name Space Window

Nieuw namespace window

In het window dat verschijnt hoeven we alleen het Name veld in te vullen. De rest is al met de juiste waardes voor ons ingevuld. Belangrijk hierbij is de tekst in het import veld. De waarde ‘private Smalltalk.*’ zorgt ervoor dat we in de classes die we definiëren in onze namespace gebruik kunnen maken van de classes en globals die onder de Smalltalk namespace vallen. Dit is belangrijk want hier vallen de belangrijke objecten als Object en Class onder alsmede de meeste utility classes zoals de collection hiërarchie en interface structuren. Het woord ‘private’ voor de declaratie zorgt ervoor dat namespace die onze classes importeren niet ook de Smalltalk namespace importeren.

Voor onze applicatie definiëren we de namespace ‘SogyoGalgje’. Voor een namespace naam gelden de zelfde restricties en conventies als voor class namen. Om conflicten met eventuele classes te voor komen heb we hier de namespace geprefixt met ‘Sogyo’. Dit zorgt ervoor dat we later een class Galgje kunnen zonder dat er onduidelijkheid over bestaat of de namespace of de class bedoeld wordt.

De structuur met namespaces en packages zoals hier besproken is inherent aan VisualWorks. Andere Smalltalk dialecten hebben andere stucturen om deployment units en scoping te definiëren.

Een class toevoegen

Een nieuwe class toevoegen in de system browser

Een nieuwe class toevoegen in de system browser

Een nieuwe toevoegen kan door weer rechts te clicken in het classes venster. Dit is het venster waar nu onze namespace ‘SogyoGalgje’ in zou moeten staan en waar we voorheen ook op geclickt hadden om een nieuwe namespace aan te maken.

New Class Window

Nieuw Class Window

Selecteer de optie ‘new class…’ en er verschijnt een window waarin we de verschillende eigenschappen voor een class kunnen invullen. Package en namespace zullen al correct ingevuld zijn, maar Naam is leeg en superclass verwijst naar ‘Core.Object’.

Volgens de methode van Test Driven Design willen we eerst onze test class aanmaken voordat we code gaan schrijven voor onze applicatie. Deze test class gaat galgje testen, dus we noemen de class ‘GalgjeTest’. Om gebruik te maken van het SUnit test framework moet onze class overerven van de class ‘XProgramming.SUnit.TestCase’. Voor mensen die op de ‘…’ knop naast het veld clicken is dit de eerste TestCase entry in de lijst.

In het veld instance variables kunnen eventueel al velden ingevuld worden die je denkt nodig te gaan hebben in de class. Dit is een lijst van namen, gescheiden door spaties. Voor onze test class hebben we geen instance variables nodig dus we laten het veld leeg. De checkboxen onder het instance variables veld bieden de mogelijkheid om bepaalde methodes automatisch te laten creëren. Accessors maakt methodes om de instance variables aan te spreken. Initializer maakt een methode die bij het aanmaken van een nieuwe instantie aangeroepen wordt zodat de class daar initialisatie logica kan bevatten en Subclass responsibilities maakt methodes aan dit methodes van de superclass overriden waar deze superclass aangaf dat het een verantwoordelijkheid was van de subclass of in meer gebruikelijke termen: het creëren van method stubs voor abstracte methodes. Gaan van deze opties hebben we nodig, dus voor onze testclass laten we ze allemaal uitstaan.

Methodes toevoegen

Een nieuw protocol toevoegen aan een class in de system browser

Een nieuw protocol toevoegen aan een class in de system browser

Voordat we daadwerkelijk een methode toevoegen is het gebruikelijk om een protocol aan te maken. Een protocol is een groepering van methodes. Er is geen directe technische consequentie aan verbonden. Het enige effect is dat er in de browser gefilterd kan worden op methodes van een bepaald protocol.

Een protocol kan toegevoegd worden door met de rechter muis knop te clicken in het protocol venster. Dit is het venster rechts naast het class venster waar onze namespace ‘SogyoGalgje’ en class ‘GalgjeTest’ in staan. Let erop dat we de class ‘GalgjeTest’ geselecteerd hebben omdat we nu een protocol gaan toevoegen aan deze class. In het context menu kies nu de optie ‘new’ om een nieuw protocol aan te maken.

New Protocol Window

Nieuw Protocol Window

Er verschijnt een scherm waarop we ons nieuwe protocol kunen aangeven. VisualWorks geeft verschillende suggesties overwerk protocol we zouden willen. Er zijn conventies voor verschillende protocollen. Zo kunnen accessors onder het protocol accessing vallen. We willen graag een protocol waaronder we onze unit tests kunnen scharen. Zodra we beginnen met typen maakt VisualWorks weer een aantal suggesties. Geen van deze suggesties zijn relevant voor ons dus we typen gewoon ‘tests’ in en accepteren dit door op OK te clicken.

Misschien viel tijdens het in tikken van tests op dat VisualWorks het protocol ‘testing’ voorstelde. Waarom hebben we dan een protocol tests aangemaakt in plaats van deze te gebruiken. ‘testing’ is echter een protocol dat gebruikt wordt voor methodes die iets testen in de class of omgeving, niet voor unit tests.

Nadat we dit gedaan hebben bevat het protocol venster een entry ‘(tests)’. De haakjes geven aan dat er nog geen methodes onder dit protocol vallen. Het is nu tijd om een methode toe te voegen aan dit protocol. Als we ons protocol selecteren dan valt misschien op dat in het onderste gedeelte een stuk tekst is verschenen. Dit is een skelet voor een methode in Smalltalk. De eerste regel is de message definitie. Een eenvoudige definitie is bijvoorbeeld ‘doeDit’. Dit geeft aan dat deze methode wordt aangeroepen als het bericht ‘doeDit’ verstuurd wordt naar het object. Deze definitie heeft geen argumenten (een unairy message). Een argument in een definitie komt na een dubbele punt. Bijvoorbeeld ‘voegToe: anObject aan: aCategory’ is een methodedefinitie met twee argumenten: anObject en aCategory (Dit is een keyword message). Deze argumenten worden lokale variabelen die in de methode gebruikt kunnen worden, net als bij andere talen.

Het sturen van een bericht naar een object heeft de vorm ‘<object> <bericht>.’. Dus als we een object myObject hebben waar we onze ‘voegToe:aan:’ bericht naar toe willen sturen dan zou dat er uit zien als: “mijnObject voegToe: ‘hello’ aan: ‘greetings’.”.

Voor onze methode willen we testen of onze Galgje applicatie het woord op de juiste manier laat zien. Als we het woord ‘hond’ in gedachte nemen dan zou er ‘____’ (vier underscores) getoond moeten worden aan het begin. We beginnen dus met een methode die ‘testGetoondWoord’ heet. We hebben geen argumenten nodig. We selecteren de tekst in het onderste venster.

message selector and argument names
“comment stating purpose of message”

| temporary variable names |
statements

Dit skeleton vervangen we door onze methode door dit te selecteren en de naam van onze methode in te tikken.

testGetoondWoord

Op de volgende regel schrijven we een stuk commentaar waarin we omschrijven wat onze methode gaat doen. Commentaar staat tussen dubbel quotes, dus ons commentaar zou kunnen zijn:

“Ik test of het antwoord op het bericht getoondWoord correct gemaskerd is.”

Commentaar in Smalltalk heeft meestal een bepaald jargon. Er wordt vaak geschreven in de eerste persoon vanuit de methode en het aanroepende object wordt beschreven als de sender terwijl er naar het object waar de methode wordt uitgevoerd wordt gerefereerd als de ontvanger. De volgende regel kan eventuele lokale variabelen bevatten die we nodig gaan hebben. Lokale variabelen worden weer gegeven als een spatie delimited lijst tussen twee pipe symbolen. Voor onze test hebben we alleen een variabele nodig voor ons galgje object. De declaratie ziet er dan als volgt uit:

| galgje |

Nu kunnen we met de echte methode beginnen. Als eerste zullen we een nieuw galgje object moet aan maken en dit toekennen aan onze lokale variabele. Toekennen werkt met de ‘:=’ operator en om een nieuw object aan te maken kunnen we de ‘new’ message naar een class sturen. We willen echter niet zo maar een galgje object, we willen een galgje object met een specifiek woord dat we straks kunnen testen. We willen een galgje met het woord ‘hond’ voor onze test. Het ‘new’ bericht heeft echter geen argumenten, daarom bedenken we een nieuw bericht die dat wel heeft.

galgje := Galgje met: ‘hond’.

Dus we sturen het bericht ‘met:’ naar de class Galgje en kennen het resultaat toe aan de lokale variable galgje. Het statement wordt afgesloten met een punt. Het woord hond tussen enkele quotes is een string. Nu rest ons te testen of het getoonde woord daadwerkelijk ‘____’ is. Dit kan door een vergelijking te doen:

galgje getoondWoord = ‘____’.

We sturen dus het bericht “getoondWoord” naar galgje en sturen het bericht “= ‘____'” naar het resultaat. Prioritiet van berichten is dat unaire berichten altijd voor gaan, daarna komen binaire berichten en uit eindelijk keyword berichten. Unaire berichten zijn berichten zonder argumenten. ‘getoondWoord’ is een unair bericht. Binare berichten zijn berichten met speciale tekens als +,- of =. De vergelijking is dus een binair bericht. Keyword berichten zijn tenslotte berichten met argumenten. Het bericht “met:” is een keyword bericht. Dus het unaire bericht “galgje getoondWoord” wordt uitgevoerd voordat de vergelijking wordt uitgevoerd. Om het resultaat van deze test door te geven aan ons framework gebruiken het bericht “assert:”. Dit is een methode van onze superclass dus het test object snapt dit bericht. Het is ook een keyword bericht, dus het heeft een lagere prioriteit dan het binaire bericht van de “= ‘____'” message. We kunnen dus schrijven:

self assert: galgje getoondWoord = ‘____’.

Het woord self representeert hierbij het object zelf. In veel talen is dit impliciet, maar in Smalltalk moet een bericht altijd naar een expliciet vernoemd object gestuurd worden ook als dit het huidige object is. De methode is nu af en het geheel ziet er als volgt uit:

testGetoondWoord
“Ik test of het antwoord op het bericht getoondWoord correct gemaskerd is.”
| galgje |
galgje := Galgje met: ‘hond’.
self assert: galgje getoondWoord = ‘____’.

Foutmelding bij opslaan testGetoondWoord methode

Foutmelding bij opslaan testGetoondWoord methode

We slaan de methode op door ‘Ctrl+s’ te tikken. Schrik niet er komen nu een aantal foutmeldingen voorbij. We hebben namelijk nog helemaal geen class Galgje gemaakt en de berichten “met:” en “getoondWoord” zijn VisualWorks onbekend. VisualWorks houdt een lijst bij waarin alle symbolen beschreven zijn. De foutmeldingen over “met:” en “getoondWoord” geven aan dat er volgens VisualWorks nog geen enkel object is dat deze twee boodschappen accepteert. Dit klopt want we hebben de class die de boodschappen moet gaan accepteren nog niet aan gemaakt. Er wordt ons de mogelijkheid aangeboden om het te corrigeren naar een wel bestaand bericht, gewoon door te gaan met dit niet bestaande bericht of te annuleren en de methode niet op te slaan.

ss14-new-classVoor ons is het niet erg dat de berichten nog niet bestaan, we gaan ze later aanmaken, dus we kunnen op ‘proceed’ clicken. De volgende foutmelding die voorbij komt is de melding waarin VisualWorks ons vraagt wat we met de term ‘Galgje’ willen. De mogelijkheden variëren van toevoegen aan de lokale variabelen tot het aan maken van een nieuwe class. Dit laatste is wat we willen dus clicken we op ‘Create Class…’.

Het new class window verschijnt en is al ingevuld met alle waarden die we willen hebben. Het enige wat we nog hoeven doen is op ‘OK’ te clicken en de class Galgje wordt aangemaakt in het Galgje package onder de SogyoGalgje namespace.

In het venster van de classes naast ons package zien we nu de Galgje class prijken boven de class GalgjeTest en en de namespace ‘SogyoGalgje’. De class bevat nog geen methodes of instance velden maar dit is geen probleem. Die gaan we nu toevoegen.

De methode testGetoondWoord is opgeslagen en de methodenaam is zichtbaar in het meest rechtse venster van de system browser. Tevens zien we dat het protocol ‘tests’ nu niet meer tussen haakjes staat. Dit komt omdat er nu een methode bij het protocol hoort. De laatste verandering is dat er nu onder het venster waar we de methode hebben ingevoerd nu een reeks knoppen verschenen is die betrekking hebben op het draaien van tests. Hiermee kunnen we de test die we zojuist geschreven hebben uitvoeren. Laten we het eens proberen. Click op de knop ‘run’.

Foutmelding tijdens debug run van test

Foutmelding tijdens debug run van test

Onze test gaat nog niet goed. Het gebied links van de knoppen wordt rood en de tekst “Failed: 1 run, 0 failed, 1 errors” geeft aan dat er een fout is opgetreden tijdens het draaien van de test. Om de fout op te sporen kunnen we nu de test nog een keer starten, maar nu door op de knop ‘debug’ te clicken. De reactie is onmiddelijk. Er komt een foutmelding naar boven “Message not understood: #met:”. Dit klopt: in onze test sturen we het bericht “met: ‘hond'” naar de class Galgje maar deze class snapt dit bericht niet. Er is geen methode in de class die past bij het bericht. Gelukkig bied Smalltalk ook verschillende oplossingen aan. Met de optie ‘debug’ kunnen we de debugger starten zodat we kunnen achterhalen waar het fout ging. Proceed negeert de foutmelding en gaat gewoon door en met Terminate hebben we de mogelijkheid om de excecutie maar helemaal te stoppen.

Scherm om de methode te definieren

Scherm om de methode te definieren

We weten echter al wat er fout gaat, namelijk we sturen een bericht en de class begrijpt dit bericht nog niet omdat we nog geen methode hiervoor gedefinieerd hebben. We willen dus de methode definiëren. Hiervoor hebben we de optie “define it…”. Selecteer deze optie.

Er verschijnt een scherm met een stub voor de “met:” methode gebaseerd op het bericht dat we naar de Galgje class stuurden. Let erop dat we dit bericht stuurden naar de class niet naar een instantie. De methode die we definiëren is een class methode. Dit is te zien in de bovenste regel van het stackwindow: “SogyoGalgje.Galgje class>>met:”. Een class methode is te vergelijken met een static methode in Java of C#.

Als antwoord op het bericht “met:” verwachten we een Galgje object in onze test. In andere talen zou de methode vergleken kunnen worden met een constructor van de class. Smalltalk kent echter het begrip constructor niet en maakt geen verschil tussen een static methode of een constructor. Het zijn allemaal class methodes. Sterker nog zelfs het commando om een nieuw object aan te maken is een class method. “new” is een methode van class, niet een speciaal keyword of instructie zoals in statische talen.

Voor onze methode willen we dus een nieuwe instantie van Galgje teruggeven. De eerste stap in onze methode is dus het aanmaken van een nieuw object. Zoals hierboven besproken kan dit door het bericht “new” naar de class te sturen: “Galgje new.”. We bevinden ons echter al in de class Galgje dus het bericht ziet er als volgt uit:

self new.

self refereert nu naar de Galgje class niet een instantie van de Galgje class. new is een unaire message dus het heeft de hoogste prioriteit we kunnen ons vervolg bericht er gewoon aan rijgen. In de nieuw aangemaakte instantie willen we het woord op het argument van deze methode zetten. Dit zouden we kunnen doen door bijvoorbeeld de accessor van het woord aan te roepen op de instantie. Dan maken we wel al aannames over de accessors en de state van het Galgje object. Conventie is dat we dit liever zo lang mogelijk willen uitstellen. Dit doen we door direct het ontvangen bericht van de class kant door te geven aan de instance kant:

self new met: aString.

We sturen nu dus de boodschap “met:” naar onze nieuw aangemaakte instantie. Dit is dus hetzelfde bericht, maar we sturen het naar een ander object, namelijk het Galgje object ipv de Galgje class. Onze methode is bijna klaar. Het enige wat ontbreekt is dat we de instantie die we net aangemaakt hebben nog moeten teruggeven. Het default gedrag van een methode is om zichzelf als resultaat terug te geven. De methode geeft nu dus de class terug als resultaat. We willen echter het object teruggeven. Hiervoor is het speciale return teken “^” voor nodig. Het statement “^ <object>.” stopt de methode en geeft het object als resultaat terug. Dit gebeurt nadat het object geëvalueerd is dus we kunnen het gewoon voor ons commando zetten. Onze complete methode ziet er nu als volgt uit:

met: aString

^self new met: aString.

We slaan deze methode op door weer “Ctrl+s” in te tikken. Dit keer krijgen we geen melding over de “met:” boodschap naar het Galgje object. Niet omdat het Galgje object deze boodschap snapt, maar omdat we net deze methode aangemaakt hebben bestaat er een “met:” bericht in het systeem en klaagt VisualWorks er niet meer over.

We hebben deze methode toegevoegd aan de class omdat we bezig waren met het draaien van de test. Deze test loopt ondertussen nog steeds. Tijdens het schrijven van de methode is de executie blijven steken net voor deze methode. We hoeven onze test dus niet opnieuw op te starten we kunnen in het “Unhandled Exception” Window op de groene pijl clicken en we gaan gewoon verder waar we gebleven waren. Dus click maar op de run pijl en we krijgen weer een nieuwe foutmelding. Het bericht “met:” bestaat niet. Maar deze hebben we toch net aangemaakt? Wat is er aan de hand? In onze vorige methode stuurden we het bericht “met:” door naar de instantie. De methode bestaat wel als class methode, maar nog niet als instance methode. We kunnen dus weer “define it…” kiezen en dit keer de instance methode “met:” implementeren.

In deze methode hoeven we geen instantie meer aan te maken, deze is al aangemaakt. We willen de instantie initialiseren. Het argument van de methode moet worden opgeslagen in de instance variable woord. Dit kan door de assignment operator “:=” net zoals we eerder onze galgje instantie aan een lokale variabele toekenden.

woord := aString.

Daarna willen we de instantie van het galgje object terug geven. Waar halen we die instantie vandaan? Dat is het object zelf. Deze methode was namelijk een methode van het object Galgje, dus we maken de methode compeet door self te retourneren.

^self

Het bovenstaande statement geeft het object zelf als return waarde van de methode. Zichzelf terug geven was echter de default actie van een methode in een object, dus we kunnen deze regel gewoon weglaten en gedrag blijft gelijk:

met: aString
woord := aString.

Als we nu deze methode opslaan met “Ctrl+s” krijgen we een fout melding. “woord” is onbekend en we kunnen weer uit een hoop mogelijkheden kiezen wat we willen doen met deze term. Voor galgje is het de bedoeling dat het woord bewaard blijft voor de instantie. Het moet dus een instance variable worden. Dit is de tweede optie van het lijstje en we kunnen weer op de groene run driehoek van het “Unhandled Exception” window clicken om  verder te gaan.

Het begint misschien al weer een beetje repetatief te worden maar er gaat weer iets “fout”. Na het uitvoeren van de eerste regel van de test (galgje := Galgje met: ‘hond’.) komen we nu bij de volgende regel daar sturen we de message getoondWoord naar het galgje object. Deze methode zou een string moeten teruggeven waar elke nog niet geraden letter vervangen werd door een “_”. Ook deze methode gaan we implementeren.

Wat we nu willen doen is het transformeren van het woord ‘hond’, een string object, naar de string ‘____’, ook een string object. Een string in smalltalk is een collectie. Een collectie heeft een aantal specifieke methodes om deze te manipuleren. “select:” om bepaalde elementen te selecteren, “reject:” om bepaalde elementen te verwijderen, “detect:” om een specifiek element te zoeken, “inject:into:” om een bepaalde waarde accumuleren en “collect:” om een transformatie op de collectie uit te voeren. Deze methodes retourneren een nieuwe collectie van het zelfde type als de oude collectie en passen de oude collectie niet aan. De meeste acties waar over een collectie geïtereerd moet worden kan met één of meerdere van deze methodes. In ons geval willen we het woord transformeren dus de collect methode is relevant:

^woord collect: [:element| self maskeer: element]

We geven hier het resultaat van de collect terug waarbij we elk element in onze collectie maskeren. Maskeer is een nieuwe methode van het galgje object die we nog niet geschreven hebben. De blokhaken definiëren een Smalltalk block: een stuk code dat als argument meegegeven kan worden aan methodes of variabelen. element staat hier voor een waarde die de methode collect aan het block geeft. De collect methode roept voor elk element in de collectie het code block aan en geeft het betreffende element als argument mee.

Als we weer verder gaan krijgen we de melding dat de methode ‘maskeer:’ niet bestaat dus we implementeren deze ook. Hier willen we het character “_” terug geven als de letter nog niet geraden is. Als de letter is geraden dan willen we gewoon de letter terug geven.

maskeer: aCharacter
(self geraden: aCharacter) ifTrue: [^aCharacter].
^$_

Hier lopen we voor het eerst tegen een probleem met prioriteiten aan. De geraden en ifTrue berichten hebben dezelfde prioriteit. Smalltalk verwacht dat we het over het bericht “geraden:ifTrue:” hebben. Daarom zetten we haakjes om de eerste aanroep heen. Dit geeft aan dat “self geraden: aCharacter” geëvalueerd moet worden voordat de ifTrue message naar het resultaat gestuurd moet worden. De ifTrue message voert het meegeleverde code block uit als de ontvanger een instantie van de class True is. Het $ zorgt ervoor dat het volgende character als als een character behandeld wordt. We slaan de methode op en gaan weer verder met het uitvoeren van onze test. Dit keer bestaat de geraden methode nog niet dus we implementeren deze methode.

geraden: aCharacter
^self letters includes: aCharacter

Om te zien of een letter geraden is vragen we de collectie van gekozen letters op en sturen de includes: boodschap. De includes boodschap is een boodschap die alle collecties begrijpen en antwoorden true als het argument in de collectie voorkomt. Save en continue en we komen er achter dat de methode letters niet bestaat. Misschien vroeg je je af waarom hier een bericht naar self gestuurd was in plaats van gewoon letters aan te spreken als instance variable. De reden daarvoor is dat als ik een nieuwe instance variable zou gaan gebruiken in de geraden methode deze variable nog geen waarde zou hebben. Onze instantie van Galgje is namelijk nog steeds dezelfde instantie die we in het begin met “Galgje met: ‘hond'” aangemaakt hebben. We zijn nog steeds in de debug run van de test methode met dit object. In de letter accessor kunnen we de variabele initialiseren indien deze nog niet geïnitialiseerd was.

letters
letters ifNil: [ letters := OrderedCollection new].
^letters

De boodschap ifNil voert het code block uit als letters nil (of null voor anderstaligen) is. Omdat de instance variable nog niet bestaat en hierna aangemaakt wordt, wordt het code block uitgevoerd en de variable geïnitialiseerd als een OrderedCollection. OrderedCollection is een collectie die een lijst representeert, vergelijkbaar met ArrayList of List in Java en C#. Bij het opslaan vraagt Smalltalk nog wat we willen doen met letters. Dit keer willen we letters als een instance variabele nemen, de tweede optie en we gaan weer verder door op het groene run driehoekje te clicken.

Nu komen we geen fouten meer tegen en onze test is groen. We hebben tijdens het draaien van de test alle fouten opgelost zodat de test slaagt. De andere acties die we op ons Galgje kunnen toepassen kunnen we op een soortgelijke manier implementeren. Voor de deelnemers aan onze Smalltalk werkgroep is de uitwerking te vinden in de Storerepository onder de naam Hangman.

Het leuke van dit voorbeeld vind ik dat we volgens goed test driven design onze test eerst geschreven hebben en alle andere methodes en classes afhankelijk hebben laten zijn van de test. Veel Java en C# ontwikkelaars beweren dat test first als ontwikkel methode niet echt werkt. Misschien hebben ze wel gelijk. Het werkt niet voor een statische taal, je hebt er een krachtige dynamische taal voor nodig met ondersteuning voor een “rewindable” debugger.


Werken met en ?
Kijk dan bij onze mogelijkheden voor starters en/of ervaren IT'ers.


Categorieën: Development, Java (EE), Overige talen en platformen

Tags: , ,


Reactie

  • Ah bedankt, dit maakt het weer een stuk duidelijker. Vooral het verschil tussen de “met: class method” en de “met: instance method” was mij nog niet helemaal duidelijk geworden tijdense de sessie.

    Geplaatst op 25 maart 2009 om 13:15 Permalink