09. Classes
Jusqu'à présent, nous avons vu de nombreux types d'objets différents, ou classes : chaînes, entiers, flottants, tableaux et quelques objets spéciaux (true, false et nil), sur lesquels nous reviendrons plus tard. En Ruby, toutes ces classes commencent toujours par une majuscule : String, Integer (Entiers), Float (Flottants), Array (Tableaux), etc. Généralement, si nous voulons créer un nouvel objet d'une certaine classe, nous utilisons new :
a = Array.new + [12345] # Addition de Tableaux.
b = String.new + 'bonjour' # Addition avec Chaînes.
c = Time.new
puts 'a = '+a.to_s
puts 'b = '+b.to_s
puts 'c = '+c.to_s
Comme nous pouvons créer des tableaux et des chaînes en utilisant [...] et '...' respectivement, nous utilisons rarement new pour cela (De toute façon, il n'est pas très clair, dans l'exemple précédent, que String.new crée une chaîne vide et que Array.new crée un tableau vide). Les nombres, cependant, sont une exception : vous ne pouvez pas créer un entier en utilisant Integer.new. Vous devez simplement taper le nombre.
La classe Time
D'accord, et la classe Time ? Les objets Time représentent des moments dans le temps. Vous pouvez ajouter (ou soustraire) des nombres à (ou de) des temps pour obtenir de nouveaux instants : ajouter 1.5 à un temps renvoie un nouvel instant une seconde et demie plus tard :
temps = Time.new # L'instant où vous chargez cette page.
temps2 = temps + 60 # Une minute plus tard.
puts temps
puts temps2
Vous pouvez également créer un temps pour un moment spécifique en utilisant Time.mktime :
puts Time.mktime(2000, 1, 1) # An 2000.
puts Time.mktime(1976, 8, 3, 10, 11) # Année de ma naissance.
Note : quand je suis né, l'Heure Avancée du Pacifique (PDT, en anglais) était en vigueur. Quand l'an 2000 est arrivé, cependant, l'Heure Normale du Pacifique (PST, en anglais) était en vigueur, au moins pour nous sur la côte Ouest. Les parenthèses servent à grouper les paramètres pour mktime. Plus vous ajoutez de paramètres, plus votre instant deviendra précis.
Vous pouvez comparer deux temps en utilisant les méthodes de comparaison (un temps antérieur est plus petit qu'un temps postérieur).
Quelques Choses à Essayer
- Un milliard de secondes... Trouvez la seconde exacte de votre naissance (si vous le pouvez). Découvrez quand vous aurez (ou quand vous avez eu ?) un milliard de secondes. Ensuite, allez le marquer sur votre calendrier.
- Joyeux Anniversaire ! Demandez l'année de naissance d'une personne. Ensuite, demandez le mois et, enfin, le jour. Ensuite, découvrez l'âge de cette personne et donnez-lui une FESSÉE ! pour chaque anniversaire qu'elle a eu.
La Classe Hash
Une autre classe très utile est la classe Hash. Les hashs sont très similaires aux tableaux : ils ont un tas d'emplacements qui peuvent contenir divers objets. Cependant, dans un tableau, les emplacements sont alignés en ligne, et chacun est numéroté (en commençant par zéro). Dans un Hash, cependant, les emplacements ne sont pas alignés en ligne (ils sont juste en quelque sorte tous ensemble), et vous pouvez utiliser n'importe quel objet pour faire référence à un emplacement, pas seulement un nombre. Il est bon d'utiliser des hashs lorsque vous avez un tas de choses que vous voulez stocker, mais qui ne s'intègrent pas vraiment dans une liste ordonnée. Par exemple, les couleurs que j'utilise dans diverses parties de ce tutoriel :
tableauCouleurs = [] # comme Array.new
hashCouleurs = {} # comme Hash.new
tableauCouleurs[0] = 'rouge'
tableauCouleurs[1] = 'vert'
tableauCouleurs[2] = 'bleu'
hashCouleurs['chaines'] = 'rouge'
hashCouleurs['nombres'] = 'vert'
hashCouleurs['mots_cles'] = 'bleu'
tableauCouleurs.each do |couleur|
puts couleur
end
hashCouleurs.each do |typeCode, couleur|
puts typeCode + ': ' + couleur
end
Si j'utilise un tableau, je dois me rappeler que l'emplacement 0 est pour les chaînes, l'emplacement 1 est pour les nombres, etc. Mais si j'utilise un Hash, c'est facile ! L'emplacement 'chaines' stocke la couleur des chaînes, bien sûr. Rien à retenir. Vous avez peut-être remarqué que lorsque j'ai utilisé each, les objets dans le hash ne sont pas sortis dans le même ordre que je les ai mis (Du moins pas quand j'ai écrit ça. Peut-être que maintenant ils le font... on ne sait jamais avec les hashs). Les tableaux servent à mettre les choses en ordre, les Hashs non.
Bien que les gens utilisent généralement des chaînes pour nommer les emplacements dans un hash, vous pouvez utiliser n'importe quel type d'objet, même des tableaux et d'autres hashs (bien que je ne puisse pas trouver une raison pour laquelle vous voudriez faire ça...) :
hashBizarre = Hash.new
hashBizarre[12] = 'singes'
hashBizarre[[]] = 'vide'
hashBizarre[Time.new] = 'rien de mieux que le Présent'
Les hashs et les tableaux sont bons pour différentes choses : c'est à vous de décider lequel résout le mieux votre problème, et différent pour tous les problèmes que vous aurez.
Étendre les Classes
À la fin du dernier chapitre, vous avez écrit une méthode pour retourner un nombre en toutes lettres. Cependant, ce n'était pas une méthode d'entiers : c'était une méthode générique du programme. Ne serait-ce pas plus cool si vous pouviez écrire 22.en_lettres au lieu de nombreFrancais 22 ? Regardez comment vous pouvez faire ça :
class Integer
def en_lettres
if self == 5
lettres = 'cinq'
else
lettres = 'cinquante-huit'
end
lettres
end
end
# Je préfère toujours tester par paires...
puts 5.en_lettres
puts 58.en_lettres
Eh bien, j'ai testé ; et rien n'a explosé. :)
Nous avons défini une méthode d'entier en "sautant" simplement dans la classe Integer, en définissant la méthode là-dedans et en ressortant. Maintenant, tous les entiers ont cette méthode sensationnelle (incomplète). En fait, si vous n'aimez pas la façon dont la méthode native to_s fait les choses, vous pouvez simplement la redéfinir... mais je ne recommande pas de faire ça ! Il est préférable de laisser les anciennes méthodes tranquilles et d'en faire de nouvelles quand vous avez besoin de quelque chose de nouveau.
Encore confus ? Laissez-moi revenir un peu sur ce dernier programme. Jusqu'à présent, chaque fois que nous exécutions du code ou définissions une méthode, nous le faisions dans l'objet "programme" par défaut. Dans notre dernier programme, nous avons quitté cet objet pour la première fois et sommes allés dans la classe Integer. Nous avons défini une méthode là-bas (ce qui en a fait une méthode d'entier) et tous les entiers peuvent l'utiliser. À l'intérieur de cette méthode, nous utilisons self pour faire référence à l'objet (l'entier) qui utilise la méthode.
Créer des Classes
Nous avons déjà vu un tas d'objets de différentes classes. Cependant, il est facile de créer des types d'objets que Ruby n'a pas. Heureusement, créer une nouvelle classe est tout aussi facile que d'en étendre une existante. Disons que nous voulions lancer des dés en Ruby. Regardez comment nous pouvons faire une classe appelée De :
class De
def rouler
1 + rand(6)
end
end
# Faisons deux dés...
des = [De.new, De.new]
# ...et roulons chacun d'eux.
des.each do |de|
puts de.rouler
end
(Si vous avez sauté la section sur les nombres aléatoires, rand(6) renvoie simplement un nombre aléatoire entre 0 et 5).
C'est tout ! Des objets de notre propre création. Roulez les dés quelques fois (en utilisant le bouton "Actualiser" de votre navigateur) et voyez ce qui se passe.
Nous pouvons définir toutes sortes de méthodes pour nos objets... mais il manque quelque chose. Travailler avec ces objets n'a pas beaucoup changé depuis que nous avons appris à manipuler des variables. Regardez notre dé, par exemple. Chaque fois que nous le roulons, nous obtenons un nombre différent. Mais si nous voulions sauvegarder ce nombre, nous devrions créer une variable pour pointer vers lui. Et tout dé décent devrait avoir un nombre, et rouler le dé devrait changer ce nombre. Si nous gardons le dé, nous n'avons aucun moyen de savoir quel nombre il affiche.
Cependant, si nous essayons de stocker le nombre que nous avons obtenu dans une variable (locale) à l'intérieur de rouler, la valeur sera perdue dès que rouler sera terminé. Nous avons besoin de sauvegarder ce nombre dans un type différent de variable :
Variables d'Instance
Normalement, quand nous parlons de chaînes, nous les appelons simplement des chaînes. Cependant, nous pourrions les appeler des Objets de type Chaîne. Parfois, certains programmeurs peuvent les appeler des instances de la classe String, mais c'est une façon exagérée (et très longue) de dire chaîne. Une instance d'une classe est juste un objet de cette classe.
Par conséquent, les variables d'instance sont comme des variables d'objet. Une variable locale d'une méthode reste en vie jusqu'à ce que la méthode se termine. Une variable d'instance d'un objet, par contre, restera en vie tant que l'objet sera en vie. Pour différencier les variables d'instance des variables locales, elles ont un @ devant leurs noms :
class De
def rouler
@nombreAffiche = 1 + rand(6)
end
def affiche
@nombreAffiche
end
end
de = De.new
de.rouler
puts de.affiche
puts de.affiche
de.rouler
puts de.affiche
puts de.affiche
Très sympa ! Maintenant rouler roule le dé et affiche nous dit quel est le nombre qui est sorti. Mais et si nous voulions voir quel nombre est sorti avant de rouler le dé (avant d'avoir défini @nombreAffiche) ?
class De
def rouler
@nombreAffiche = 1 + rand(6)
end
def affiche
@nombreAffiche
end
end
# Puisque je ne vais plus utiliser ce dé,
# je n'ai pas besoin de le sauvegarder dans une variable.
puts De.new.affiche
Hum... Eh bien, au moins ça n'a pas donné d'erreur. Attendez, ça n'a pas beaucoup de sens un dé "non-roulé" ou quoi que nil signifie ici. Ce serait beaucoup plus cool si nous pouvions rouler le dé dès qu'il est créé. C'est ce que fait initialize :
class De
def initialize
# Je vais juste rouler le dé, bien que
# nous puissions faire n'importe quoi que
# nous voulions, comme mettre la face '6'
# vers le haut
rouler
end
def rouler
@nombreAffiche = 1 + rand(6)
end
def affiche
@nombreAffiche
end
end
puts De.new.affiche
Quand un objet est créé, la méthode initialize (si elle a été définie) est toujours appelée.
Notre dé est presque parfait. La seule chose qui manque est un moyen de définir quel nombre est affiché... Pourquoi n'écririez-vous pas la méthode triche qui fait ça ? Revenez quand vous aurez fini (et quand vous l'aurez testé et que ça marchera, bien sûr). Assurez-vous simplement que personne ne puisse faire afficher un 7 au dé !
C'était très cool ce que nous avons fait jusqu'à présent. Mais c'était juste un jouet, quand même. Laissez-moi vous montrer un exemple plus intéressant. Faisons un animal virtuel, un bébé dragon. Comme tous les bébés, il doit pouvoir manger, dormir et "faire ses besoins", ce qui signifie que nous allons devoir pouvoir le nourrir, le mettre au lit et l'emmener dans le jardin. En interne, notre dragon a besoin de savoir s'il a faim, s'il est fatigué ou s'il a besoin de sortir, mais nous ne pourrons pas voir cela pendant que nous interagissons avec lui, tout comme vous ne pouvez pas demander à un bébé "as-tu faim ?". Donc nous allons ajouter quelques façons sympas d'interagir avec notre bébé dragon, et quand il naîtra nous lui donnerons un nom (Tout ce que vous passez comme paramètre à la méthode new sera passé à la méthode initialize pour vous). D'accord, essayons :
class Dragon
def initialize nom
@nom = nom
@endormi = false
@nourritureEstomac = 10 # Il est plein
@nourritureIntestin = 0 # Il n'a pas besoin d'aller au jardin
puts @nom + ' est né.'
end
def nourrir
puts 'Vous avez nourri ' + @nom + '.'
@nourritureEstomac = 10
passageDuTemps
end
def jardin
puts 'Vous avez emmené ' + @nom + ' au jardin.'
@nourritureIntestin = 0
passageDuTemps
end
def mettreAuLit
puts 'Vous avez mis ' + @nom + ' au lit.'
@endormi = true
3.times do
if @endormi
passageDuTemps
end
if @endormi
puts @nom + ' ronfle et remplit la chambre de fumée.'
end
end
if @endormi
@endormi = false
puts @nom + ' se réveille.'
end
end
def lancer
puts 'Vous lancez ' + @nom + ' en l\'air.'
puts 'Il glousse et brûle vos sourcils.'
passageDuTemps
end
def bercer
puts 'Vous bercez ' + @nom + ' doucement.'
@endormi = true
puts 'Il commence à somnoler...'
passageDuTemps
if @endormi
@endormi = false
puts '...mais se réveille quand vous arrêtez.'
end
end
private
# "private" signifie que les méthodes définies ici
# sont des méthodes internes de l'objet. (Vous pouvez
# le nourrir, mais vous ne pouvez pas lui demander si
# il a faim.)
def aFaim?
# Les noms de méthodes peuvent finir par "?".
# Normalement, nous faisons cela seulement
# si la méthode renvoie vrai ou faux,
# comme celle-ci :
@nourritureEstomac <= 2
end
def aBesoinDeSortir?
@nourritureIntestin >= 8
end
def passageDuTemps
if @nourritureEstomac > 0
# Déplacer la nourriture de l'estomac vers l'intestin.
@nourritureEstomac = @nourritureEstomac - 1
@nourritureIntestin = @nourritureIntestin + 1
else # Notre dragon est affamé !
if @endormi
@endormi = false
puts 'Il se réveille !'
end
puts @nom + ' est affamé ! En désespoir de cause, il vous a mangé VOUS !'
exit # Cela quitte le programme.
end
if @nourritureIntestin >= 10
@nourritureIntestin = 0
puts 'Oups ! ' + @nom + ' a eu un accident...'
end
if aFaim?
if @endormi
@endormi = false
puts 'Il se réveille !'
end
puts 'L\'estomac de ' + @nom + ' gargouille...'
end
if aBesoinDeSortir?
if @endormi
@endormi = false
puts 'Il se réveille !'
end
puts @nom + ' fait la danse pour aller au jardin...'
end
end
end
animal = Dragon.new 'Norbert'
animal.nourrir
animal.lancer
animal.jardin
animal.mettreAuLit
animal.bercer
animal.mettreAuLit
animal.mettreAuLit
animal.mettreAuLit
animal.mettreAuLit
WOUAH ! Bien sûr, ce serait beaucoup plus cool si c'était un programme interactif, mais vous pouvez faire cette partie plus tard. J'essayais juste de vous montrer les parties liées directement à la création d'une nouvelle classe de type Dragon.
Nous avons dit un tas de nouvelles choses dans cet exemple. La première est simple : exit termine le programme où qu'il soit. La deuxième est le mot private, que nous avons mis juste au milieu de notre classe. J'aurais pu le laisser de côté, mais je voulais juste renforcer l'idée que certaines méthodes vous pouviez faire avec un dragon, tandis que d'autres se produisaient avec le dragon. Vous pouvez penser à cela comme "des choses sous le capot" : à moins que vous ne soyez un mécanicien automobile, tout ce que vous avez besoin de savoir sur les voitures, c'est l'accélérateur, le frein et la direction. Un programmeur appelle cela l'interface publique.
Maintenant, pour un exemple plus concret dans cette ligne de raisonnement, parlons un peu de comment vous représenteriez une voiture dans un jeu (ce qui est mon domaine de travail). D'abord, vous devez décider à quoi ressemblera votre interface publique ; en d'autres termes, quelles méthodes les gens peuvent-ils appeler sur vos objets de type voiture ? Eh bien, ils doivent pouvoir accélérer et freiner, mais ils doivent aussi pouvoir définir la force qu'ils appliquent sur la pédale (Il y a une grande différence entre effleurer l'accélérateur et écraser le pied). Ils auront aussi besoin de conduire, et encore une fois, dire quelle force ils appliquent sur la direction. Je pense que vous pouvez aller encore plus loin et ajouter un embrayage, des clignotants, un lance-roquettes, un incinérateur arrière, un convecteur temporel, etc... cela dépend du type de jeu que vous faites.
Les objets internes à une voiture, cependant, sont plus complexes : d'autres choses dont une voiture a besoin sont la vitesse, la direction et la position (en restant basique). Ces attributs seront modifiés en appuyant sur la pédale d'accélérateur ou de frein et en tournant le volant, bien sûr, mais l'utilisateur ne devrait pas pouvoir modifier ces informations directement (ce qui serait une distorsion). Vous voudrez peut-être vérifier le dérapage ou les dommages, la résistance de l'air et ainsi de suite. Tout cela ne concerne que la voiture. Tout cela est interne à la voiture.
Quelques Choses à Essayer
- Faites une classe de
Oranger. Elle doit avoir une méthodehauteurqui renvoie sa hauteur, une méthode appeléepasser_un_anqui, lorsqu'elle est appelée, fait compléter une année de plus à l'arbre. Chaque année, l'arbre grandit (peu importe combien grand vous pensez qu'un oranger peut grandir en un an), et après quelques années (encore une fois, vous décidez) l'arbre doit mourir. Les premières années, il ne doit pas produire de fruits, mais après un certain temps il doit, et je pense que les arbres plus vieux produisent beaucoup plus de fruits qu'un plus jeune avec le passage des années... ou ce que vous trouvez le plus logique. Et, bien sûr, vous devez pouvoircompter_les_oranges(le nombre d'oranges sur l'arbre), etprendre_une_orange(qui réduira le@nombre_d_orangesde un et renverra une chaîne disant combien l'orange était délicieuse, ou alors dira qu'il n'y a plus d'oranges cette année). Rappelez-vous que les oranges que vous ne prenez pas cette année doivent tomber avant l'année prochaine. - Écrivez un programme pour que vous puissiez interagir avec votre bébé dragon. Vous devez être capable d'entrer des commandes comme
nourriretjardin, et ces méthodes doivent être appelées sur votre dragon. Logiquement, comme toute l'entrée se fera par des chaînes, vous devez avoir un moyen de répartir les méthodes, où votre programme doit valider la chaîne tapée et appeler la méthode appropriée.
Et c'est tout ! Mais attendez un peu... Je ne vous ai rien dit sur les classes pour faire des choses comme envoyer un e-mail, ou enregistrer et charger des fichiers de votre ordinateur, ou comment créer des fenêtres et des boutons, ou des mondes en 3D ou quoi que ce soit ! Eh bien, il y a juste trop de classes que vous pouvez utiliser, et cela rend impossible que je vous les montre toutes ; même moi je ne les connais pas toutes. Ce que je peux vous dire, c'est où en trouver plus à leur sujet, afin que vous puissiez en apprendre plus sur celles que vous voulez utiliser. Mais avant de vous renvoyer, il y a une autre fonctionnalité de Ruby que vous devriez connaître, quelque chose que la plupart des autres langages n'ont pas, mais dont je ne peux tout simplement pas me passer : les blocs et les procs.