Ajoutez dans le même module une nouvelle classe,
Image.
Elle doit posséder quatre données membres :
- une chaîne de caractères name, pour le nom de l’image,
- deux entiers (usize) width et height, qui indiqueront
respectivement sa largeur et sa hauteur,
- et pixels, un vecteur de Color qui servira pour stocker les
pixels de l’image.
Il est fondamental de comprendre que la classe
Image doit assurer un
invariant.
En effet, le nombre des pixels doit toujours être égal à la dimension de
l’image déclarée dans ses attributs
width et
height.
En d’autres mots :
length of pixels == width * height
Cela implique que les données membres ne doivent surtout pas être accessibles
librement et elles doivent être
encapsulées.
En Rust, les données membres d’une classe sont encapsulées (c’est à dire
privées) par défaut et le contrôle de l’accès aux données se fait au niveau
des modules :
- un module a accès à toutes les ressources déclarées dans le module même et
dans les modules qui le contiennent,
- un module n’a pas accès librement aux ressources déclarées dans les sous
modules (sauf si tout ce qui mène à ces ressources est déclaré public).
En revanche, la classe
Image devra être accessible partout, donc
n’oubliez pas de la déclarer publique.
Étant donné que les membres de la classe
Image sont privés, nous devons
fournir tous les services qui lui sont spécifiques.
En Rust, comme en C++, il est possible de définir les services d’une classe
à travers des fonctions membres et des fonctions non-membres.
Étant donné qu’en Rust, toutes les données membres d’une structure sont
librement accessibles partout dans le même module, il n’y a pas vraiment
de raison pour choisir d’utiliser un type de fonction par rapport à
l’autre.
Nous suivrons alors une règle très simple et nous nous forcerons d’écrire
des fonctions membres pour tout service qui est propre au type de donnée
représenté par la classe.
Une fonction qui aura juste besoin de se servir d’objets de ce type, sera
plutôt définie comme fonction non-membre.
Pour rappel, toute fonction membre d’une classe est définie dans son
implémentation,
c’est à dire de son bloc
impl.
Écrivez le bloc
impl et définissez une première fonction membre qui
permet la création et l’initialisation d’une image.
Cette fonction doit s’appeler
new_unicolor(), doit recevoir en
paramètre tout ce qu’il faut pour initialiser les données membres de
l’image (nom, largeur, hauteur et une couleur) et doit retourner un
Self (un objet du type concerné).
Attention ! Évidemment, il faudra remplir le vecteur
pixels
de
width*height couleurs toutes pareilles (la même que celle passée
en paramètre).
Pour tester cette première fonction, instanciez dans le
main() un objet
de type
Image en lui passant comme couleur une des deux couleurs
instanciées précédemment.
Affichez à l’écran le nom et les dimensions de l’image.
Par exemple :
Image "red" with size 3×2
Vous remarquerez tout de suite que le compilateur n’accepte pas d’accéder
directement aux données membres de la classe.
En effet, la fonction
main() est hors du module déclarant le type
Image et donc, à cause de l’encapsulation, n’a le droit ni de
consulter ni de modifier directement les données membres de la classe
Image.
L’implémentation d’
Image devra alors permettre la consultation du nom
et des dimensions (ne définissez qu’une seule fonction pour lire la largeur
et la hauteur et renvoyez un
tuple).
Pour rappel, il est tout à fait normal que le compilateur vous informe que
le membre
pixels n’est pas encore utilisé.