syntax: 'meth' -> 'fun', 'attr' -> 'var'
[nit.git] / tests / example_objet.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2004-2008 Jean Privat <jean@pryen.org>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Exemple commenté d'un programme en NIT
18 # Cet exemple est tiré du dernier TP C++ de l'année des IUP1
19
20 # Attention, NIT est un langage enrichi en sucre (syntaxique) et en déclaration
21 # implicite. Cet exemple en introduit petit à petit. Et chaque utilisation est
22 # abondamment commentée. D'ailleurs, a propos de commentaires, il est peut-être
23 # trop tard pour dire que ceux-ci commencent par un dièse (#)
24
25 # Les points suivants sont couverts par cet exemple :
26 # - définition des modules
27 # - définition des classes
28 # - définition des propriétés (attributs, procédures, fonctions)
29 # - structures de contrôles (blocs, boucles, conditions)
30 # - variables locales
31 # - accesseurs universels
32 # - constructeurs et constructions d'instances
33 # - chaînes de caractères
34
35 # Les points suivants sont aperçus :
36 # - classes génériques
37 # - protection (visibilité des propriétés)
38 # - collections
39 # - héritage et redéfinition
40
41 # Les points suivants ne sont pas abordés :
42 # - accesseurs implicites
43 # - (re)définition des opérateurs
44 # - paramètres implicites
45 # - méthodes à arité variable
46 # - méthodes retardées (virtuelles pures)
47 # - constructeurs universels
48 # - appel à super
49 # - héritage multiple
50 # - covariance des types
51 # - raffinement de classes
52
53 # Un fichier = un module. Les classes d'un module sont définies dans le fichier.
54 # Au début des fichiers, les modules à importer doivent être déclarés (via le
55 # mot clé "import"). Implicitement, tout module importe le module nommé
56 # "standard" qui définit les classes usuelles.
57
58 # On va étudier une représentation d'un entrepôt de produits variés.
59 class Entrepot
60 private
61 # Dans un bloc "private", les propriétés déclarées sont seulement accessibles au
62 # receveur ("self")
63
64 # Les attributs sont déclarés par le mot clé "fun" et commencent par un "@"
65 var _nom_: String # Un entrepôt a un nom (de type chaîne).
66 var _rayons: Array[Rayon] # Il est composé d'un ensemble de rayon.
67 # "Array" est une classe paramétrée, les crochets en sont la marque.
68 # La classe "Rayon" est définie plus loin
69
70
71 # Les propriétés déclarées dans un bloc "public" sont accessibles à tout le
72 # monde.
73
74 # Les méthodes (fonctions et procédures) sont déclarées par le mot clé "fun"
75 # nom est une fonction qui retourne une chaîne
76 fun nom: String
77 do
78 # "return" sort de la fonction
79 return _nom_
80 # En fait, les attributs et les méthodes sont dans deux espaces
81 # de noms distincts.
82 # Le @ peut se prononcer "at" ce qui rappelle le mot attribut
83 end
84
85 # set_nom est une procédure qui prend une chaîne en paramètre
86 fun set_nom(n: String)
87 do
88 _nom_ = n # "=" désigne l'affectation
89 # Les affectations sont des instructions et ne sont pas
90 # chaînées
91 end
92
93 fun add_rayon(r: Rayon)
94 do
95 _rayons.add(r) # "add" ajoute un élément
96 end
97
98 # to_s est la méthode implicitement appelée par "print" et "println"
99 # Il s'agit de la représentation "humaine" d'un objet
100 redef fun to_s: String
101 do
102 # Les variables sont déclarées par "var", leur portée va de leur
103 # déclaration jusqu'au "end" correspondant
104 var s = new Buffer # Là où on calcule le résultat
105 # Les chaînes littérales sont déclarées avec des guillemets
106 s.append("*** Entrepôt ") # On initialise "s"
107 # puis on concatène des chaînes à "s"
108 s.append(_nom_) # la méthode "append" concatène
109 s.append(" ***\n")
110 # Les conditions sont de la forme "if then else if else end"
111 if _rayons.is_empty then
112 s.append("L'entrepôt est vide\n")
113 else
114 var i: Iterator[Rayon]
115 # Les itérateurs permettent de traverser les collections
116 i = _rayons.iterator # "iterator" retourne un nouvel itérateur
117 # La forme des boucles est "while do end"
118 while i.is_ok do # on pointe un élément valide ?
119 # "item" retourne l'élément pointé par
120 # un iterateur
121 s.append(i.item.to_s)
122 i.next # passe à l'élément suivant
123 end
124 s.add('\n') # "add" ajoute un caractère à la fin.
125 # Comme en C, les caractères sont entre simples cotes.
126 end
127 return s.to_s
128 end
129
130
131 # Dans un bloc "constructor", les méthodes déclarées sont celles utilisées pour
132 # créer des instances
133
134 # init sans paramètre est le constructeur implicite
135 init
136 do
137 _nom_ = "sans nom"
138 # "new" permet d'instancier une classe.
139 _rayons = new Array[Rayon]
140 # Un nouveau tableau est implicitement vide
141 # On aurait pu prévoir une capacité en utilisant un autre
142 # constructeur et en écrivant :
143 # _rayons = new Array[Rayon].with_capacity(50)
144 end
145
146 # Plusieurs constructeurs peuvent être définis
147 init with_nom(n: String)
148 do
149 _nom_ = n
150 _rayons = new Array[Rayon]
151 end
152
153 # Une classe qui ne possède pas de méthode définie dans un bloc "constructor"
154 # est une classe abstraite : elle ne peut être instanciée.
155 end
156
157 class Produit
158 private
159 var _nom_: String # Désignation du produit
160 var _qte_: Int # Quantité en stock
161
162
163
164 # Comme nous l'avons vu, les accesseurs en lecture sont
165 # généralement homonymes aux attributs (au @ près).
166 fun nom: String
167 do
168 return _nom_
169 end
170
171 fun qte: Int
172 do
173 return _qte_
174 end
175
176 # Toutefois, pour les accesseurs en écriture, il est d'usage en NIT
177 # d'utiliser un type de méthode particulier appelé "accesseur universel"
178 # son nom se caractérise par un "=" final
179 fun qte=(i: Int)
180 do
181 _qte_ = i
182 end
183 # La méthode s'utilise alors de façon élégante :
184 # pour p de type statique Produit, l'expression
185 # p.qte
186 # correspond à un envoi de message vers la fonction "qte" et retourne
187 # la quantité d'un produit. Tandis que l'instruction
188 # p.qte = 16
189 # correspond à un envoi de message vers la méthode "qte=" et affecte
190 # la quantité d'un produit.
191
192
193 redef fun to_s: String
194 do
195 # On peut aussi utiliser "+" pour concaténer les chaînes
196 return _nom_ + ":" + _qte_.to_s
197 # Mais ce n'est pas très efficace car cela crée des objets
198 # intermédiaires.
199 end
200
201
202 init(nom: String, qte: Int)
203 do
204 _nom_ = nom
205 _qte_ = qte
206 end
207 end
208
209 class Rayon
210 private
211 var _stock: Array[Produit] # Des produits en stock
212 var _rubrique: String # La catégorie des produits stockés
213
214 # Cette fonction est utilisé par to_s pour afficher un petit titre
215 fun to_s_head: String
216 do
217 # Les déclarations de type sont optionnelles dans un bloc "var".
218 # Si une expression est passée comme valeur initiale d'une
219 # variable, le type statique de la variable est implicitement
220 # celui de l'expression.
221 var s = new Buffer
222 s.append("* Rayon : ")
223 # Ici, le type statique de s est implicitement String
224
225 s.append(_rubrique)
226 s.add('\n')
227 return s.to_s
228 end
229
230 fun cherche_produit(n: String): nullable Produit
231 do
232 var i = _stock.iterator
233 while i.is_ok do
234 var p = i.item
235 # "=" est l'opérateur égalité de valeur.
236 if p.nom == n then
237 return p
238 end
239 i.next
240 end
241 # "null" correspond à l'objet vide
242 return null
243 end
244
245
246 redef fun to_s: String
247 do
248 var s = new Buffer
249 s.append(to_s_head)
250 # Les boucles en NIT sont des structures puissantes, toutefois
251 # la manipulation des itérateurs peut être facilité par la
252 # structure "for in"
253 for p in _stock do
254 # L'expression de droite doit être sous-type de Collection
255 # (ce qui est le cas pour Array[Produit]).
256 # La variable p est automatiquement déclarée, son type
257 # automatiquement déduit (ici Produit) et la collection est
258 # automatiquement parcourue
259 # En réalité, la structure "for in" n'est que du sucre
260 # syntaxique pour une boucle sur une collection via des
261 # itérateur comme celle écrite dans la fonction
262 # "cherche_produit" précédente
263 s.append(p.to_s)
264 s.add('\n')
265 end
266 return s.to_s
267 end
268
269
270 # Cette fonction permet de retourner la quantité d'un produit donné
271 # Ce service est très simple à implémenter si on utilise cherche_produit
272 fun quantite(n: String): Int
273 do
274 var p = cherche_produit(n)
275 # "==" est l'opérateur d'égalité d'identité
276 if p == null then
277 return 0
278 else
279 return p.qte
280 end
281 end
282
283 # Le service dual consiste à définir la quantité d'un produit
284 # En NIT, il est d'usage de définir ce genre de service par un accesseur
285 # universel :
286 fun quantite=(n: String, i: Int)
287 do
288 var p = cherche_produit(n)
289 if p == null then
290 # On Construit un nouveau produit que l'on ajoute
291 _stock.add(new Produit(n, i))
292 else
293 # On change la quantité du produit trouvé
294 p.qte = i
295 end
296 end
297 # Les accesseurs universels s'utilise de façon toujours aussi élégante.
298 # Pour r de type Rayon, on écrira donc :
299 # r.quantite("clous")
300 # pour obtenir la quantité de clous dans le rayon, et :
301 # r.quantite("clous") = 15
302 # pour mettre le nombre de clous à 15
303
304 init do end
305 end
306
307 class RayonNormal
308 special Rayon
309
310 init(r: String)
311 do
312 _rubrique = r
313 _stock = new Array[Produit]
314 end
315 end
316
317 class RayonFroid
318 # Les super-classes sont déclarés avec le mot clé "special".
319 # Implicitement, c'est "Object", la classe de tous les objets.
320 special Rayon
321
322 # Tant qu'on parle d'implicite, en l'absence de bloc de propriétés,
323 # celles-ci sont déclarées en tant que "public"
324 fun temp_max: Int
325 do
326 return _temp_max_ # Attribut défini juste en dessous
327 end
328
329 var _temp_max_: Int # Une température maximale
330 # Les autres attributs sont bien sûr hérités !
331
332 redef fun to_s_head: String # On redéfinit cette méthode
333 do
334 # nous avons vu le "append" et le "+" sur les chaînes, mais la
335 # méthode la plus simple reste la construction "{}" qui
336 # permet l'inclusion automatique d'expressions. C'est aussi la méthode
337 # la plus efficace.
338 return "* Rayon Réfrigéré : {_rubrique} - t° max : {_temp_max_}\n"
339 end
340
341
342 init(r: String, t: Int)
343 do
344 _temp_max_ = t
345 _rubrique = r
346 _stock = new Array[Produit]
347 end
348 end
349
350
351
352 # Le main (point d'entrée du programme) peut être déclaré directement à la fin
353 # des définitions de classes
354
355 # Constructeur implicite ("init")
356 var e = new Entrepot
357 # "println" affiche un truc et ajoute un \n à la fin
358 print(e.nom) # affiche "sans nom"
359 # "print" affiche un truc mais sans le \n à la fin
360 printn('\n') # va à la de ligne
361
362 e.set_nom("Montpellier")
363 printn(e.nom, "\n\n")
364 # Affiche "Montpellier" et saute une ligne.
365 # Contrairement à "println", "print" peut prendre plusieurs paramètres
366
367 # Constructeur explicite ("with_nom")
368 e = new Entrepot.with_nom("Lunel") # on perd l'ancien entrepôt
369 printn(e, '\n')
370 # affiche "*** Entrepot Lunel *** L'entrepôt est vide"
371
372 var p = new Produit("Carotte", 15)
373 print(p) # affiche Carotte:15
374 p.qte = 20
375 print(p) # affiche Carotte:20
376 printn('\n')
377
378 var r = new RayonNormal("Légumes")
379 printn(r, '\n')
380
381 r.quantite("Carotte") = 15
382 r.quantite("Navet") = 10
383 r.quantite("Chou") = 3
384 printn(r)
385 r.quantite("Chou") = 13
386 r.quantite("Courge") = 1
387 printn(r, '\n')
388
389 e.add_rayon(r)
390
391 var r2 = new RayonFroid("Surgelés", -5)
392 e.add_rayon(r2)
393 r2.quantite("Pizza") = 12
394 r2.quantite("Poisson pané") = 4
395 printn(e)