IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Cours complet Pharo par l'exemple


précédentsommaire

2. Chapitre 2 - Une première application

Dans ce chapitre, nous allons développer un jeu simple de réflexion, le jeu Lights Out(18). En cours de route, nous allons présenter la plupart des outils que les développeurs Pharo utilisent pour construire et déboguer leurs programmes et voir comment les programmes sont échangés entre les développeurs. Nous verrons notamment le navigateur de classes, l'inspecteur d'objet, le débogueur et le navigateur de paquetages Monticello. Le développement avec Smalltalk est efficace : vous découvrirez que vous passerez beaucoup plus de temps à écrire du code et beaucoup moins à gérer le processus de développement. Ceci est en partie dû au fait que Smalltalk est un langage très simple et d'autre part que les outils qui forment l'environnement de programmation sont très intégrés avec le langage.

2-1. Le jeu Lights Out

Image non disponible
FIGURE 2.1 – Le plateau de jeu Lights Out. L'utilisateur vient de cliquer sur une case avec la souris comme le montre le curseur.

Pour vous montrer comment utiliser les outils de développement de Pharo, nous allons construire un jeu très simple nommé Lights Out. Le plateau de jeu est montré dans la figure 2.1 ; il consiste en un tableau rectangulaire de cellules jaunes claires. Lorsque l'on clique sur l'une de ces cellules avec la souris, les quatre qui l'entourent deviennent bleues. Cliquez de nouveau et elles repassent au jaune pâle. Le but du jeu est de faire passer au bleu autant de cellules que possible.

Le jeu Lights Out montré dans la figure 2.1 est fait de deux types d'objets : le plateau de jeu lui-même et une centaine de cellules-objets individuelles. Le code Pharo pour réaliser ce jeu va contenir deux classes : une pour le jeu et une autre pour les cellules. Nous allons voir maintenant comment définir ces deux classes en utilisant les outils de programmation de Pharo.

2-2. Créer un nouveau paquetage

Nous avons déjà vu le Browser dans le chapitre 1, où nous avons appris à naviguer dans les classes et les méthodes et à définir de nouvelles méthodes. Nous allons maintenant voir comment créer des paquetages (ou packages), des catégories et des classes.

Ouvrez un Browser et cliquez avec le bouton d'action sur le panneau des paquetages. Sélectionnez create package (19) .

Tapez le nom du nouveau paquetage (nous allons utiliser PBE-LightsOut) dans la boîte de dialogue et cliquez sur accept (ou appuyez simplement sur la touche entrée) ; le nouveau paquetage est créé et s'affiche dans la liste des paquetages en respectant l'ordre alphabétique.

2-3. Définir la classe LOCell

Pour l'instant, il n'y a aucune classe dans le nouveau paquetage. Cependant, le panneau de code inférieur — qui est la zone principale d'édition — affiche un patron pour faciliter la création d'une nouvelle classe (voir la figure 2.3).

Ce modèle nous montre une expression Smalltalk qui envoie un message à la classe appelée Object, lui demandant de créer une sous-classe appelée NameOfSubClass. La nouvelle classe n'a pas de variables et devrait appartenir à la catégorie PBE-LightsOut.

Image non disponible
FIGURE2.2 – Ajouter un paquetage.
Image non disponible
FIGURE2.3 – Le patron de création d’une classe.

2-3-1. À propos des catégories et des paquetages

Historiquement, Smalltalk ne connaît que les catégories. Vous pouvez vous interroger sur la différence qui peut exister entre catégories et paquetages. Une catégorie est simplement une collection de classes apparentées dans une image Smalltalk. Un paquetage (ou package) est une collection de classes apparentées et de méthodes d'extension qui peuvent être versionnées avec l'outil de versionnage Monticello. Par convention, les noms de paquetages et les noms de catégories sont les mêmes. D'ordinaire, nous ne faisons pas de différence, mais dans ce livre, nous serons attentifs à utiliser la terminologie exacte, car il y a des cas où la différence est cruciale. Vous en apprendrez plus lorsque nous aborderons le travail avec Monticello.

2-3-2. Créer une nouvelle classe

Nous modifions simplement le modèle afin de créer la classe que nous souhaitons.

Image non disponible

Modifiez le modèle de création d'une classe comme suit :

  • remplacez Object par SimpleSwitchMorph ;
  • remplacez NameOfSubClass par LOCell ;
  • ajoutez mouseAction dans la liste de variables d'instances.

Le résultat doit ressembler à la classe 2.1.

Classe 2.1 – Définition de la classe LOCell
Sélectionnez
1.
2.
3.
4.
5.
SimpleSwitchMorph subclass: #LOCell
  instanceVariableNames: 'mouseAction'
  classVariableNames: ''
  poolDictionaries: ''
  category: 'PBE-LightsOut'

Cette nouvelle définition consiste en une expression Smalltalk qui envoie un message à une classe existante SimpleSwitchMorph, lui demandant de créer une sous-classe appelée LOCell (en fait, comme LOCell n'existe pas encore, nous passons comme argument le symbole #LOCell qui correspond au nom de la classe à créer). Nous indiquons également que les instances de cette nouvelle classe doivent avoir une variable d'instance mouseAction, que nous utiliserons pour définir l'action que la cellule doit effectuer lorsque l'utilisateur clique dessus avec la souris.

À ce point, nous n'avons encore rien construit. Notez que le bord du panneau du modèle de la classe est passé au rouge (voir la figure 2.4). Cela signifie qu'il y a des modifications non sauvegardées. Pour effectivement envoyer ce message, vous devez faire accept.

Image non disponible
FIGURE 2.4 – Le modèle de création d'une classe.

Acceptez la nouvelle définition de classe.

Cliquez avec le bouton d'action et sélectionnez accept ou encore utilisez le raccourci-clavier CMD–s (pour « save » c.-à-d. sauvegarder). Ce message sera envoyé à SimpleSwitchMorph, ce qui aura pour effet de compiler la nouvelle classe.

Une fois la définition de classe acceptée, la classe va être créée et apparaîtra dans le panneau des classes du navigateur (voir la figure 2.5). Le panneau d'édition montre maintenant la définition de la classe et un petit panneau dessous vous invite à écrire quelques mots décrivant l'objectif de la classe. Nous appelons cela un commentaire de classe ; il est assez important d'en écrire un qui donnera aux autres développeurs une vision globale de votre classe. Les Smalltalkiens accordent une grande valeur à la lisibilité de leur code et il n'est pas habituel de trouver des commentaires détaillés dans leurs méthodes ; la philosophie est plutôt d'avoir un code qui parle de lui-même (si cela n'est pas le cas, vous devrez le refactoriser jusqu'à ce que ça le soit !). Un commentaire de classe ne nécessite pas une description détaillée de la classe, mais quelques mots la décrivant sont vitaux si les développeurs qui viennent après vous souhaitent passer un peu de temps sur votre classe.

Image non disponible
FIGURE 2.5 – La classe nouvellement créée LOCell. Le panneau inférieur est le panneau de commentaires ; par défaut, il dit : « CETTE CLASSE N'A PAS DE COMMENTAIRE ! ».

Tapez un commentaire de classe pour LOCell et acceptez-le ; vous aurez tout le loisir de l'améliorer par la suite.

2-4. Ajouter des méthodes à la classe

Ajoutons maintenant quelques méthodes à notre classe.

Sélectionnez le protocole --all-- dans le panneau des protocoles.

Vous voyez maintenant un modèle pour la création d'une méthode dans le panneau d'édition. Sélectionnez-le et remplacez-le par le texte de la méthode 2.2.

Méthode 2.2 – Initialiser les instances de LOCell
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
initialize
  super initialize.
  self label: ''.
  self borderWidth: 2.
  bounds := 0@0 corner: 16@16.
  offColor := Color paleYellow.
  onColor := Color paleBlue darker.
  self useSquareCorners.
  self turnOff

Notez que les caractères '' de la ligne 3 sont deux apostrophes(20) sans espace entre elles et non un guillemet (") ! '' représente la chaîne de caractères vide.

Faites un accept de cette définition de méthode.

Que fait le code ci-dessus ? Nous n'allons pas rentrer dans tous les détails maintenant (ce sera l'objet du reste de ce livre !), mais nous allons vous en donner un aperçu. Reprenons le code ligne par ligne.

Notons que la méthode s'appelle initialize. Ce nom dit bien ce qu'il veut dire(21) ! Par convention, si une classe définit une méthode nommée initialize, cette méthode sera appelée dès que l'objet aura été créé. Ainsi, dès que nous évaluons LOCell new, le message initialize sera envoyé automatiquement à cet objet nouvellement créé. Les méthodes d'initialisation sont utilisées pour définir l'état des objets, généralement pour donner une valeur à leurs variables d'instances ; c'est exactement ce que nous faisons ici.

La première action de cette méthode (ligne 2) est d'exécuter la méthode initialize de sa superclasse, SimpleSwitchMorph. L'idée est que tout état hérité sera initialisé correctement par la méthode initialize de la superclasse. C'est toujours une bonne idée d'initialiser l'état hérité en envoyant super initializeavant de faire toute autre chose ; nous ne savons pas exactement ce que la méthode initialize de SimpleSwitchMorph va faire et nous ne nous en soucions pas, mais il est raisonnable de penser que cette méthode va initialiser quelques variables d'instance avec des valeurs par défaut et qu'il vaut mieux le faire au risque de se retrouver dans un état incorrect.

Le reste de la méthode donne un état à cet objet. Par exemple, envoyer self label: '' affecte le label de cet objet avec la chaîne de caractères vide.

L'expression 0@0 corner: 16@16 nécessite probablement plus d'explications. 0@0 représente un objet Point dont les coordonnées x et y ont été fixées à 0. En fait, 0@0 envoie le message @ au nombre 0 avec l'argument 0. L'effet produit sera que le nombre 0 va demander à la classe Point de créer une nouvelle instance de coordonnées (0,0). Puis, nous envoyons à ce nouveau point le message corner: 16@16, ce qui cause la création d'un Rectangle de coins 0@0et 16@16. Ce nouveau rectangle va être affecté à la variable bounds héritée de la superclasse.

Notez que l'origine de l'écran Pharo est en haut à gauche et que les coordonnées en y augmentent vers le bas.

Le reste de la méthode doit être compréhensible de lui-même. Une partie de l'art d'écrire du bon code Smalltalk est de choisir les bons noms de méthodes de telle sorte que le code Smalltalk puisse être lu comme de l'anglais simplifié (English pidgin). Vous devriez être capable d'imaginer l'objet se parlant à lui-même et dire : « Utilise des bords carrés ! » (d'où useSquareCorners), « Éteins les cellules ! » (en anglais, turnOff).

2-5. Inspecter un objet

Vous pouvez tester l'effet du code que vous avez écrit en créant un nouvel objet LOCell et en l'inspectant avec l'inspecteur nommé Inspector.

Ouvrez un espace de travail (Workspace). Tapez l'expression LOCell new et choisissez inspect it.

Le panneau gauche de l'inspecteur montre une liste de variables d'instances ; si vous en sélectionnez une (par exemple bounds), la valeur de la variable d'instance est affichée dans le panneau droit.

Le panneau en bas d'un inspecteur est un mini espace de travail. C'est très utile, car dans cet espace de travail, la pseudo-variable self est liée à l'objet sélectionné.

Image non disponible
FIGURE 2.6 – L'inspecteur utilisé pour examiner l'objet LOCell.

Sélectionnez LOCell à la racine de la fenêtre de l'inspecteur. Saisissez l'expression self bounds: (200@200 corner: 250@250) dans le panneau inférieur et faîtes un do it (via le menu contextuel ou le raccourci-clavier).

La variable bounds devrait changer dans l'inspecteur. Saisissez maintenant self openInWorld dans ce même panneau et évaluez le code avec do it. La cellule doit apparaître près du coin supérieur gauche, là où les coordonnées bounds doivent le faire apparaître. Meta-cliquez sur la cellule afin de faire apparaître son halo Morphic.

Déplacez la cellule avec la poignée marron (à gauche de l'icône du coin supérieur droit) et redimensionnez-la avec la poignée jaune (en bas à droite). Vérifiez que les limites indiquées par l'inspecteur sont modifiées en conséquence (il faudra peut-être cliquer avec le bouton d'action sur refresh pour voir les nouvelles valeurs).

Image non disponible
FIGURE 2.7 – Redimensionner la cellule.

Détruisez la cellule en cliquant sur le x de la poignée rose pâle (en haut à gauche).

2-6. Définir la classe LOGame

Créons maintenant l'autre classe dont nous avons besoin dans le jeu ; nous l'appellerons LOGame.

Faites apparaître le modèle de définition de classe dans la fenêtre principale du navigateur.

Pour cela, cliquez sur le nom du paquetage. Éditez le code de telle sorte qu'il puisse être lu comme suit, puis faites accept.

Classe 2.3 – Définition de la classe LOGame
Sélectionnez
1.
2.
3.
4.
5.
BorderedMorph subclass: #LOGame
  instanceVariableNames: ''
  classVariableNames: ''
  poolDictionaries: ''
  category: 'PBE-LightsOut'

Ici nous sous-classons BorderedMorph ; Morph est la superclasse de toutes les formes graphiques de Pharo, et (surprise !) un BorderedMorph est un Morph avec un bord. Nous pourrions également insérer les noms des variables d'instance entre apostrophes sur la seconde ligne, mais pour l'instant laissons cette liste vide.

Définissons maintenant une méthode initialize pour LOGame.

Tapez ce qui suit dans le navigateur comme une méthode de LOGame et faites ensuite accept :

Méthode 2.4 – Initialisation du jeu
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
initialize
  | sampleCell width height n |
  super initialize.
  n := self cellsPerSide.
  sampleCell := LOCell new.
  width := sampleCell width.
  height := sampleCell height.
  self bounds: (5@5 extent: ((width*n) @(height*n)) + (2 * self borderWidth)).
  cells := Matrix new: n tabulate: [ :i :j | self newCellAt: i at: j ]

Pharo va se plaindre parce qu'il ne connaît pas la signification de certains termes. Il vous indique alors qu'il ne connaît pas le message cellsPerSide (en français, « cellules par côté ») et vous suggère un certain nombre de propositions, dans le cas où il s'agirait d'une erreur de frappe.

Mais cellsPerSide n'est pas une erreur — c'est juste le nom d'une méthode que nous n'avons pas encore définie — que nous allons écrire dans une minute ou deux.

Sélectionnez la première option du menu afin de confirmer que nous parlons bien de cellsPerSide .

Puis, Pharo va se plaindre de ne pas connaître la signification de cells. Il vous offre plusieurs possibilités de correction.

Image non disponible
FIGURE 2.8 – Pharo détecte un sélecteur inconnu.
Image non disponible
FIGURE 2.9 – Déclaration d'une nouvelle variable d'instance.

Choisissez declare instance parce que nous souhaitons que cells soit une variable d'instance.

Enfin, Pharo va se plaindre à propos du message newCellAt:at: envoyé à la dernière ligne ; ce n'est pas non plus une erreur, confirmez donc ce message aussi.

Si vous regardez maintenant de nouveau la définition de classe (en cliquant sur le bouton instance), vous allez voir que la définition a été modifiée pour inclure la variable d'instance cells.

Examinons plus précisément cette méthode initialize. La ligne | sampleCell width height n | déclare quatre variables temporaires. Elles sont appelées variables temporaires, car leur portée et leur durée de vie sont limitées à cette méthode. Des variables temporaires avec des noms explicites sont utiles afin de rendre le code plus lisible. Smalltalk n'a pas de syntaxe spéciale pour distinguer les constantes et les variables et en fait, ces quatre « variables » sont ici des constantes. Les lignes 4 à 7 définissent ces constantes.

Quelle doit être la taille de notre plateau de jeu ? Assez grande pour pouvoir contenir un certain nombre de cellules et pour pouvoir dessiner un bord autour d'elles. Quel est le bon nombre de cellules ? 5 ? 10 ? 100 ? Nous ne le savons pas pour l'instant et si nous le savions, il y aurait des chances pour que nous changions d'idée par la suite. Nous déléguons donc la responsabilité de connaître ce nombre à une autre méthode, que nous appelons cellsPerSide et que nous écrirons bientôt. C'est parce que nous envoyons le message cellsPerSide avant de définir une méthode avec ce nom que Pharo nous demande « confirm, correct, or cancel » (c.-à-d. « confirmez, corrigez ou annulez ») lorsque nous acceptons le corps de la méthode initialize. Que cela ne vous inquiète pas : c'est en fait une bonne pratique d'écrire en fonction d'autres méthodes qui ne sont pas encore définies. Pourquoi ? En fait, ce n'est que quand nous avons commencé à écrire la méthode initialize que nous nous sommes rendu compte que nous en avions besoin, et à ce point, nous lui avons donné un nom significatif et nous avons poursuivi, sans nous interrompre.

La quatrième ligne utilise cette méthode : le code Smalltalk self cellsPerSide envoie le message cellsPerSide à self, c.-à-d. à l'objet lui-même. La réponse, qui sera le nombre de cellules par côté du plateau de jeu, est affectée à n.

Les trois lignes suivantes créent un nouvel objet LOCell et assignent sa largeur et sa hauteur aux variables temporaires appropriées.

La ligne 8 fixe la valeur de bounds (définissant les limites) du nouvel objet. Ne vous inquiétez pas trop sur les détails pour l'instant. Croyez-nous : l'expression entre parenthèses crée un carré avec comme origine (c.-à-d. son coin haut à gauche) le point (5,5) et son coin bas droit suffisamment loin afin d'avoir de l'espace pour le bon nombre de cellules.

La dernière ligne affecte la variable d'instance cells de l'objet LOGame à un nouvel objet Matrix avec le bon nombre de lignes et de colonnes. Nous réalisons cela en envoyant le message new:tabulate: à la classe Matrix (les classes sont des objets aussi, nous pouvons leur envoyer des messages). Nous savons que new:tabulate: prend deux arguments parce qu'il y a deux fois deux points (:) dans son nom. Les arguments arrivent à droite après les deux points. Si vous êtes habitué à des langages de programmation où les arguments sont tous mis à l'intérieur de parenthèses, ceci peut sembler surprenant dans un premier temps. Ne vous inquiétez pas, c'est juste de la syntaxe ! Cela s'avère être une excellente syntaxe, car le nom de la méthode peut être utilisé pour expliquer le rôle des arguments. Par exemple, il est très clair que Matrix rows:5 columns:2 a 5 lignes et 2 colonnes et non pas 2 lignes et 5 colonnes.

Matrix new: n tabulate: [ :i :j | self newCellAt: i at: j ] crée une nouvelle matrice de taille n × n et initialise ses éléments. La valeur initiale de chaque élément dépend de ses coordonnées. L'élément (i,j) sera initialisé avec le résultat de l'évaluation de self newCellAt: i at: j.

2-7. Organiser les méthodes en protocoles

Avant de définir de nouvelles méthodes, attardons-nous un peu sur le troisième panneau en haut du navigateur. De la même façon que le premier panneau du navigateur nous permet de catégoriser les classes dans des paquetages de telle sorte que nous ne sommes pas submergés par une liste de noms de classes trop longue dans le second panneau, le troisième panneau nous permet de catégoriser les méthodes de telle sorte que n'ayons pas une liste de méthodes trop longue dans le quatrième panneau. Ces catégories de méthodes sont appelées « protocoles ».

Image non disponible
FIGURE 2.10 – Catégoriser de façon automatique toutes les méthodes non catégorisées.

S'il n'y avait que quelques méthodes par classe, ce niveau hiérarchique supplémentaire ne serait pas vraiment nécessaire. C'est pour cela que le navigateur offre un protocole virtuel --all-- (c.-à-d. « tout » en français) qui, vous ne serez pas surpris de l'apprendre, contient toutes les méthodes de la classe.

Si vous avez suivi l'exemple jusqu'à présent, le troisième panneau doit contenir le protocole as yet unclassified(22).

Cliquez avec le bouton d'action dans le panneau des protocoles et sélectionnez various ▷ categorize automatically afin de régler ce problème et déplacer les méthodes initialize vers un nouveau protocole appelé initialization.

Comment Pharo sait-il que c'est le bon protocole ? En général, Pharo ne peut pas le savoir, mais dans notre cas, il y a aussi une méthode initialize dans la superclasse et Pharo suppose que notre méthode initialize doit être rangée dans la même catégorie que celle qu'elle surcharge.

Une convention typographique. Les Smalltalkiens utilisent fréquemment la notation « >> » afin d'identifier la classe à laquelle la méthode appartient, par exemple, la méthode cellsPerSide de la classe LOGame sera référencée par LOGame>>cellsPerSide. Afin d'indiquer que cela ne fait pas partie de la syntaxe de Smalltalk, nous utiliserons plutôt le symbole spécial » de telle sorte que cette méthode apparaîtra dans le texte comme LOGame»cellsPerSide.

À partir de maintenant, lorsque nous voudrons montrer une méthode dans ce livre, nous écrirons le nom de cette méthode sous cette forme. Bien sûr, lorsque vous tapez le code dans un navigateur, vous n'avez pas à taper le nom de la classe ou le » ; vous devrez juste vous assurer que la classe appropriée est sélectionnée dans le panneau des classes.

Définissons maintenant les autres méthodes qui sont utilisées par la méthode LOGame»initialize. Les deux peuvent être mises dans le protocole initialization.

Méthode 2.5 – Une méthode constante
Sélectionnez
1.
2.
3.
LOGame»cellsPerSide
  "Le nombre de cellules le long de chaque côté du jeu"10

Cette méthode ne peut pas être plus simple : elle retourne la constante 10. Représenter les constantes comme des méthodes a comme avantage que si le programme évolue de telle sorte que la constante dépende d'autres propriétés, la méthode peut être modifiée pour calculer la valeur.

Méthode 2.6 – Une méthode auxiliaire pour l'initialisation
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
LOGame»newCellAt: i at: j
  "Crée une cellule à la position (i,j) et l'ajoute dans ma représentation graphique à la position correcte. Retourne une nouvelle cellule"
  | c origin |
  c := LOCell new.
  origin := self innerBounds origin.
  self addMorph: c.
  c position: ((i - 1) * c width) @ ((j - 1) * c height) + origin.
  c mouseAction: [self toggleNeighboursOfCellAt: i at: j]

Ajoutez les méthodes LOGame»cellsPerSide et LOGame»newCellAt:at:.

Confirmez que les sélecteurs toggleNeighboursOfCellAt:at: et mouseAction: s'épellent correctement.

La méthode 2.6 retourne une nouvelle cellule LOCell à la position (i,j) dans la matrice (Matrix) de cellules. La dernière ligne définit l'action de la souris (mouseAction) associée à la cellule comme le bloc [self toggleNeighboursOfCellAt:i at:j]. En effet, ceci définit le comportement de rappel ou callback à effectuer lorsque nous cliquons à la souris. La méthode correspondante doit être aussi définie.

Méthode 2.7 – La méthode callback
Sélectionnez
1.
2.
3.
4.
5.
LOGame»toggleNeighboursOfCellAt: i at: j
  (i > 1) ifTrue: [ (cells at: i - 1 at: j ) toggleState].
  (i < self cellsPerSide) ifTrue: [ (cells at: i + 1 at: j) toggleState].
  (j > 1) ifTrue: [ (cells at: i at: j - 1) toggleState].
  (j < self cellsPerSide) ifTrue: [ (cells at: i at: j + 1) toggleState]

La méthode 2.7 (traduisible par « change les voisins de la cellule… ») change l'état des 4 cellules au nord, sud, ouest et est de la cellule (i, j). La seule complication est que le plateau de jeu est fini. Il faut donc s'assurer qu'une cellule voisine existe avant de changer son état.

Placez cette méthode dans un nouveau protocole appelé game logic (pour « logique du jeu ») et créé en cliquant avec le bouton d'action dans le panneau des protocoles.

Pour déplacer cette méthode, vous devez simplement cliquer sur son nom puis la glisser-déposer sur le nouveau protocole (voir la figure 2.11).

Image non disponible
FIGURE 2.11 – Faire un glisser-déposer de la méthode dans un protocole.

Afin de compléter le jeu Lights Out, nous avons besoin de définir encore deux méthodes dans la classe LOCell pour gérer les événements souris.

Méthode 2.8 – Un mutateur typique
Sélectionnez
1.
2.
LOCell»mouseAction: aBlock
  ↑ mouseAction := aBlock

La seule action de la méthode 2.8 consiste à donner comme valeur à la variable mouseAction celle de l'argument, puis à en retourner la nouvelle valeur. Toute méthode qui change la valeur d'une variable d'instance de cette façon est appelée une méthode d'accès en écriture ou mutateur (vous pourrez trouver dans la littérature le terme anglais setter) ; une méthode qui retourne la valeur courante d'une variable d'instance est appelée une méthode d'accès en lecture ou accesseur (le mot anglais équivalent est getter).

Si vous êtes habitués aux méthodes d'accès en lecture (getter) et écriture (setter) dans d'autres langages de programmation, vous vous attendez à avoir deux méthodes nommées getMouseAction et setMouseAction pour l’accès en lecture et l’accès en écriture. La convention en Smalltalk est différente. Une méthode d'accès en lecture a toujours le même nom que la variable correspondante et la méthode d'accès en écriture est nommée de la même manière avec un « : » à la fin ; ici nous avons donc mouseAction et mouseAction:.

Une méthode d'accès (en lecture ou en écriture) est appelée en anglais accessor et par convention, elle doit être placée dans le protocole accessing. En Smalltalk, toutes les variables d'instances sont privées à l'objet qui les possède, ainsi, la seule façon pour un autre objet de lire ou de modifier ces variables en Smalltalk se fait au travers de ces méthodes d'accès comme ici(23).

Allez à la classe LOCell, définissez LOCell»mouseAction: et mettez-la dans le protocole accessing.

Finalement, vous avez besoin de définir la méthode mouseUp: ; elle sera appelée automatiquement par l'infrastructure (ou framework) graphique si le bouton de la souris est pressé lorsque le pointeur de celle-ci est au-dessus d'une cellule sur l'écran.

Méthode 2.9 – Un gestionnaire d'événements
Sélectionnez
1.
2.
LOCell»mouseUp: anEvent
  mouseAction value

Ajoutez la méthode LOCell»mouseUp: définissant l'action lorsque le bouton de la souris est relâché puis, faites categorize automatically.

Que fait cette méthode ? Elle envoie le message value à l'objet stocké dans la variable d'instance mouseAction. Rappelez-vous que dans la méthode LOGame»newCellAt: i at: j nous avons affecté le fragment de code qui suit à mouseAction :

 
Sélectionnez
[self toggleNeighboursOfCellAt: i at: j ]

Envoyer le message value provoque l'évaluation de ce bloc (toujours entre crochets, voir le chapitre 3) et, par voie de conséquence, est responsable du changement d'état des cellules.

2-8. Essayons notre code

Voilà, le jeu Lights Out est complet !

Si vous avez suivi toutes les étapes, vous devriez pouvoir jouer au jeu qui comprend 2 classes et 7 méthodes.

Dans un espace de travail, tapez LOGame new openInWorld et faites do it.

Le jeu devrait s'ouvrir et vous devriez pouvoir cliquer sur les cellules et vérifier si le jeu fonctionne.

Du moins en théorie… Lorsque vous cliquez sur une cellule, une fenêtre de notification appelée la fenêtre PreDebugWindow devrait apparaître avec un message d'erreur ! Comme nous pouvons le voir sur la figure 2.12, elle dit MessageNotUnderstood: LOGame»toggleState.

Image non disponible
FIGURE2.12 – Il y a une erreur dans notre jeu lorsqu’une cellule est sélectionnée !

Que se passe-t-il ? Afin de le découvrir, utilisons l'un des outils les plus puissants de Smalltalk, le débogueur.

Cliquez sur le bouton debug de la fenêtre de notification.

Le débogueur nommé Debugger devrait apparaître. Dans la partie supérieure de la fenêtre du débogueur, nous pouvons voir la pile d'exécution, affichant toutes les méthodes actives ; en sélectionnant l'une d'entre elles, nous voyons dans le panneau du milieu le code Smalltalk en cours d'exécution dans cette méthode, avec la partie qui a déclenché l'erreur en caractère gras.

Cliquez sur la ligne nommée LOGame»toggleNeighboursOfCellAt:at: (près du haut).

Le débogueur vous montrera le contexte d'exécution à l'intérieur de la méthode où l'erreur s'est déclenchée (voir la figure 2.13).

Image non disponible
FIGURE 2.13 – Le débogueur avec la méthode toggleNeighboursOfCell:at: sélectionnée.

Dans la partie inférieure du débogueur, il y a deux petites fenêtres d'inspection. Sur la gauche, vous pouvez inspecter l'objet receveur du message qui cause l'exécution de la méthode sélectionnée. Vous pouvez voir ici les valeurs des variables d'instance. Sur la droite, vous pouvez inspecter l'objet qui représente la méthode en cours d'exécution. Il est possible d'examiner ici les valeurs des paramètres et les variables temporaires.

En utilisant le débogueur, vous pouvez exécuter du code pas à pas, inspecter les objets dans les paramètres et les variables locales, évaluer du code comme vous le faites dans le Workspace et, de manière surprenante pour ceux qui sont déjà habitués à d'autres débogueurs, il est possible de modifier le code en cours de débogage ! Certains Smalltalkiens programment la plupart du temps dans le débogueur, plutôt que dans le navigateur de classes. L'avantage est certain : la méthode que vous écrivez est telle qu'elle sera exécutée c.-à-d. avec ses paramètres dans son contexte actuel d'exécution.

Dans notre cas, vous pouvez voir dans la première ligne du panneau du haut que le message toggleState a été envoyé à une instance de LOGame, alors qu'il était clairement destiné à une instance de LOCell. Le problème se situe vraisemblablement dans l'initialisation de la matrice cells. En parcourant le code de LOGame»initialize, nous pouvons voir que cells est rempli avec les valeurs retournées par newCellAt:at:, mais lorsque nous regardons cette méthode, nous constatons qu'il n'y a pas de valeur retournée ici ! Par défaut, une méthode retourne self, ce qui dans le cas de newCellAt:at: est effectivement une instance de LOGame.

Fermez la fenêtre du débogueur. Ajoutez l'expression « c » à la fin de la méthode LOGame»newCellAt:at: de telle sorte qu'elle retourne c (voir la méthode 2.10).

Méthode 2.10 – Corriger l'erreur
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
LOGame»newCellAt: i at: j
  "Crée une cellule à la position (i,j) et l'ajoute dans ma représentation graphique à la position correcte. Retour ne une nouvelle cellule"
  | c origin |
  c := LOCell new.
  origin := self innerBounds origin.
  self addMorph: c.
  c position: ((i - 1) * c width) @ ((j - 1) * c height) + origin.
  c mouseAction: [self toggleNeighboursOfCellAt: i at: j].
  ↑ c

Rappelez-vous ce que nous avons vu dans le chapitre 1 : pour renvoyer une valeur d'une méthode en Smalltalk, nous utilisons ↑, que nous pouvons obtenir en tapant ^.

Il est souvent possible de corriger le code directement dans la fenêtre du débogueur et de poursuivre l'application en cliquant sur Proceed. Dans notre cas, la chose la plus simple à faire est de fermer la fenêtre du débogueur, détruire l'instance en cours d'exécution (avec le halo Morphic) et d'en créer une nouvelle, parce que le bug ne se situe pas dans une méthode erronée, mais dans l'initialisation de l'objet.

Exécutez LOGame new openInWorld de nouveau.

Le jeu doit maintenant se dérouler sans problèmes… ou presque ! S'il vous arrive de bouger la souris entre le moment où vous cliquez et le moment où vous relâchez le bouton de la souris, la cellule sur laquelle se trouve la souris sera aussi changée. Ceci résulte du comportement hérité de SimpleSwitchMorph. Nous pouvons simplement corriger celà en surchargeant mouseMove: pour lui dire de ne rien faire :

Méthode 2.11 – Surcharger les actions associées aux déplacements de la souris
Sélectionnez
LOGame»mouseMove: anEvent

Et voilà !

2-9. Sauvegarder et partager le code Smalltalk

Maintenant que nous avons un jeu Lights Out fonctionnel, vous avez probablement envie de le sauvegarder quelque part pour pouvoir le partager avec des amis. Bien sûr, vous pouvez sauvegarder l'ensemble de votre image Pharo et montrer votre premier programme en l'exécutant, mais vos amis ont probablement leur propre code dans leurs images et ne veulent pas s'en passer pour utiliser votre image. Nous avons donc besoin de pouvoir extraire le code source d'une image Pharo afin que d'autres développeurs puissent le charger dans leurs images.

La façon la plus simple de le faire est d'effectuer une exportation ou sortie-fichier (filing out) de votre code. Le menu activé en cliquant avec le bouton d'action dans le panneau des paquetages vous permet de générer un fichier correspondant au paquetage PBE-LightsOut tout entier via l'option various ▷ file out. Le fichier résultant est plus lisible par tout un chacun, même si son contenu est plutôt destiné aux machines qu'aux hommes. Vous pouvez envoyer par email ce fichier à vos amis et ils peuvent le charger dans leurs propres images Pharo en utilisant le navigateur de fichiers File List Browser.

Cliquez avec le bouton d'action sur le paquetage PBE-LightsOut et choisissez various ▷ file out pour exporter le contenu.

Vous devriez trouver maintenant un fichier PBE-LightsOut.st dans le répertoire où votre image a été sauvegardée. Jetez un coup d'œil à ce fichier avec un éditeur de texte.

Ouvrez une nouvelle image Pharo et utilisez l'outil File Browser (Tools ▷ File Browser) pour faire une importation de fichier grâce à l'option de menu file in du fichier PBE-LightsOut.st. Vérifiez que le jeu fonctionne maintenant dans une nouvelle image.

Image non disponible
FIGURE 2.14 – Charger le code source dans Pharo.

2-9-1. Les paquetages Monticello

Bien que les exportations de fichiers soient une façon convenable de faire des sauvegardes du code que vous avez écrit, elles font maintenant partie du passé. Tout comme la plupart des développeurs de projets libres OpenSource qui trouvent plus utile de maintenir leur code dans des dépôts en utilisant CVS(24) ou Subversion(25), les programmeurs sur Pharo gèrent maintenant leur code au moyen de paquetages Monticello (dit, en anglais, packages) : ces paquetages sont représentés comme des fichiers dont le nom se termine en .mcz ; ce sont en fait des fichiers compressés en zip qui contiennent le code complet de votre paquetage.

En utilisant le navigateur de paquetages Monticello, vous pouvez sauvegarder les paquetages dans des dépôts en utilisant de nombreux types de serveurs, notamment des serveurs FTP et HTTP ; vous pouvez également écrire vos paquetages dans un dépôt qui se trouve dans un répertoire de votre système local de fichiers. Une copie de votre paquetage est toujours en cache sur disque local dans le répertoire package-cache. Monticello vous permet de sauvegarder de multiples versions de votre programme, fusionner des versions, revenir à une ancienne version et voir les différences entre plusieurs versions. En fait, nous retrouvons les mêmes types d'opérations auxquelles vous pourriez être habitués en utilisant CVS ou Subversion pour partager votre travail.

Vous pouvez également envoyer un fichier .mcz par email. Le destinataire devra le placer dans son répertoire package-cache ; il sera alors capable d'utiliser Monticello pour le parcourir et le charger.

Ouvrez le navigateur Monticello ou Monticello Browser depuis le menu World .

Dans la partie droite du navigateur (voir la figure 2.15), il y a une liste des dépôts Monticello incluant tous les dépôts dans lesquels du code a été chargé dans l'image que vous utilisez.

Image non disponible
FIGURE 2.15 – Le navigateur Monticello.

En haut de la liste dans le navigateur Monticello, il y a un dépôt dans un répertoire local appelé package cache : il s'agit d'un répertoire-cache pour des copies de paquetages que vous avez chargées ou publiées sur le réseau. Ce cache est vraiment utile, car il vous permet de garder votre historique local. Il vous permet également de travailler là où vous n'avez pas d'accès Internet ou lorsque l'accès est si lent que vous n'avez pas envie de sauver fréquemment sur un dépôt distant.

2-9-2. Sauvegarder et charger du code avec Monticello

Dans la partie gauche du navigateur Monticello, il y a une liste de paquetages dont vous avez une version chargée dans votre image ; les paquetages qui ont été modifiés depuis qu'ils ont été chargés sont marqués d'un astérisque (ils sont parfois appelés des dirty packages). Si vous sélectionnez un paquetage, la liste des dépôts est restreinte à ceux qui contiennent une copie du paquetage sélectionné.

Ajoutez le paquetage PBE-LightsOut à votre navigateur Monticello en utilisant le bouton +Package.

2-9-3. SqueakSource : un SourceForge pour Pharo

Nous pensons que la meilleure façon de sauvegarder votre code et de le partager est de créer un compte sur un serveur SqueakSource. SqueakSource est similaire à SourceForge (26) : il s'agit d'un portail web sur un serveur Monticello HTTP qui vous permet de gérer vos projets. Il y a un serveur public SqueakSource à l'adresse http://www.squeaksource.com et une copie du code concernant ce livre est enregistrée sur http://www.squeaksource.com/PharoByExample.html.
Vous pouvez consulter ce projet à l'aide d'un navigateur Internet, mais il est beaucoup plus productif de le faire depuis Pharo en utilisant l'outil ad hoc, le navigateur Monticello, qui vous permet de gérer vos paquetages.

Ouvrez un navigateur web à l'adresse http://www.squeaksource.com. Ouvrez un compte et ensuite, créez un projet (c.-à-d. avec « register ») pour le jeu Lights Out.

SqueakSource va vous montrer l'information que vous devez utiliser lorsque nous ajoutons un dépôt au moyen de Monticello.

Une fois que votre projet a été créé sur SqueakSource, vous devez indiquer au système Pharo de l'utiliser.

Avec le paquetage PBE-LightsOut sélectionné, cliquez sur le bouton +Repository dans le navigateur Monticello.

Vous verrez une liste des différents types de dépôts disponibles. Pour ajouter un dépôt SqueakSource, sélectionnez le menu HTTP. Une boîte de dialogue vous permettra de rentrer les informations nécessaires pour le serveur. Vous devez copier le modèle ci-dessous pour identifier votre projet SqueakSource, copiez-le dans Monticello en y ajoutant vos initiales et votre mot de passe :

 
Sélectionnez
MCHttpRepository
  location: 'http://www.squeaksource.com/ VotreProjet'
  user: ' vosInitiales'
  password: ' votreMotDePasse'

Si vous passez en paramètres des initiales et un mot de passe vide, vous pouvez toujours charger le projet, mais vous ne serez pas autorisé à le mettre à jour :

 
Sélectionnez
MCHttpRepository
  location: 'http://www.squeaksource.com/SqueakByExample'
  user: ''
  password: ''

Une fois que vous avez accepté ce modèle, un nouveau dépôt doit apparaître dans la partie droite du navigateur Monticello.

Cliquez sur le bouton Save pour faire une première sauvegarde du jeu Lights Out sur SqueakSource.

Pour charger un paquetage dans votre image, vous devez d'abord sélectionner une version particulière. Vous pouvez faire cela dans le navigateur de dépôts Repository Browser, que vous pouvez ouvrir avec le bouton Open ou en cliquant avec le bouton d'action pour choisir open repository dans le menu contextuel. Une fois que vous avez sélectionné une version, vous pouvez la charger dans votre image.

Ouvrez le dépôt PBE-LightsOut que vous venez de sauvegarder.

Image non disponible
FIGURE 2.16 – Parcourir un dépôt Monticello.

Monticello a beaucoup d'autres fonctionnalités qui seront discutées plus en détail dans le chapitre 6. Vous pouvez également consulter la documentation en ligne de Monticello à l'adresse http://www.wiresong.ca/Monticello/.

2-10. Résumé du chapitre

Dans ce chapitre, nous avons vu comment créer des catégories, des classes et des méthodes. Nous avons vu aussi comment utiliser le navigateur de classes (Browser), l'inspecteur (Inspector), le débogueur (Debugger) et le navigateur Monticello.

  • Les catégories sont des groupes de classes connexes.
  • Une nouvelle classe est créée en envoyant un message à sa superclasse.
  • Les protocoles sont des groupes de méthodes apparentées.
  • Une nouvelle méthode est créée ou modifiée en éditant la définition dans le navigateur de classes et en acceptant les modifications.
  • L'inspecteur offre une manière simple et générale pour inspecter et interagir avec des objets arbitraires.
  • Le navigateur de classes détecte l'utilisation de méthodes et de variables non déclarées et propose d'éventuelles corrections.
  • La méthode initialize est automatiquement exécutée après la création d'un objet dans Pharo. Vous pouvez y mettre le code d'initialisation que vous voulez.
  • Le débogueur est une interface de haut niveau pour inspecter et modifier l'état d'un programme en cours d'exécution.
  • Vous pouvez partager le code source en sauvegardant une catégorie sous forme d'un fichier d'exportation.
  • Une meilleure façon de partager le code consiste à faire appel à Monticello afin de gérer un dépôt externe défini, par exemple, comme un projet SqueakSource.

précédentsommaire
Nous supposons que le Browser est installé en tant que navigateur de classes par défaut. Si le Browser ne ressemble pas à celui de la figure 2.2, vous aurez besoin de changer le navigateur par défaut. Voyez la FAQ 7, p. 349.
Nous utilisons le terme « quote » en anglais.
En anglais, puisque c'est la langue conventionnelle en Smalltalk.
N.D.T. : pas encore classées.
En fait, les variables d'instances peuvent être accédées également dans les sous-classes.

Licence Creative Commons
Le contenu de cet article est rédigé par Andrew Black et est mis à disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2013 Developpez.com.