Laboratoire 2 - Recettes de brochettes de fruits variés.
Exercices avancés sur la généricité.
Ingrédients
- Des classes de fruits:
Fraise
,Banane
etOrange
et leur super-classeFruit
. Comme on peut peler les bananes et les oranges (mais pas les fraises), on ajoute une super-classe communeFruitAPeler
. - Une classe Brochette. On peut embrocher et débrocher des fruits.
Recette 1 - brochette fade.
- Dans le langage Java, développez les classes de fruits.
- Développez un classe brochette non-générique (les paramètres et retours des opérations sont typés par Fruit).
- Développez le code d’exemple suivant
exemple_banane
(il devrait fonctionner).
// Pseudocode. Ajoutez les casts qui vont bien si nécessaires.
void exemple_banane() {
// Créer une brochette de fruits.
Brochette brochette = new Brochette();
// Créer une banane.
Banane banane = new Banane();
// Embrocher la banane.
brochette.embrocher(banane);
// Débrocher un fruit.
FruitAPeler f = brochette.debrocher();
// Peler le fruit débroché.
f.peler()
}
- Développez un exemple similaire
exemple_fruit
mais qui prend un fruit en paramètre:
// Pseudocode. Ajoutez les casts qui vont bien si nécessaires.
void exemple_fruit(Fruit fruit) {
// Créer une brochette de fruits.
Brochette brochette = new Brochette();
// Embrocher le fruit
brochette.embrocher(fruit);
// Débrocher un fruit.
FruitAPeler f = brochette.debrocher();
// Peler le fruit débroché.
f.peler()
}
- Développez le programme principal qui invoque
exemple_banane
, invoqueexemple_fruit
avec une instance de banane, puis invoqueexemple_fruit
avec une instance de fraise. - Répétez ces opérations (sans utiliser de généricité) pour les langage C++, C#, Python (ou Ruby) et Nit (ou Eiffel).
Discussion
Ici on a deux morceaux de code très similaires exemple_banane
et exemple_fruit
.
Le premier est systématiquement fonctionnel: il ne peut pas échouer lors de l’exécution.
Le second est partiellement fonctionnel: il y a un risque d’échec fondamental car dans notre modèle (plus ou moins réaliste), il est impossible de peler une fraise, quelque soit la façon dont on s’y prend.
Questions
Pour chacun des langages:
- Quel est le problème du premier exemple
exemple_banane
d’un point de vu de la verbosité et de la sûreté des types ? - Dans chacun des langages, où (quelle instruction) ont lieu les erreurs dans le second exemple (
exemple_fruit
) ? - Sont-elles statiques ou dynamiques ?
Recette 2 - brochette typique.
- Dans le langage Java, développez un classe brochette générique
BrochetteGen
bornée parFruit
. - Développez les deux codes d’exemples
exemple_banane_p
etexemple_fruit_p
en utilisantBrochetteGen<FruitAPeler>
au lieu deBrochette
. - Répétez ces opérations pour les langages C++, C# et Nit.
Questions
- Quels sont les problèmes résolus par la généricité (par rapport à la version non-générique) ?
- Où ont lieu les erreurs dans le second exemple (
exemple_fruit_p
) ? - Ces erreurs sont-elles statiques ou dynamiques ?
- Si on avait utilisé
BrochetteGen<Fruit>
au lieu deBrochetteGen<FruitAPeler>
quels problèmes aurait été résolus/non-résolus (par rapport à la version non-générique) ?
Recette 3 - brochette spécialisée
Cette fois-ci on fait varier le type générique.
On utilise statiquement une BrochetteGen<Fruit>
et dynamiquement une BrochetteGen<FruitAPeler>
Ce qui donne le pseudo-code suivant
void brochette_fp_fruit(Fruit fruit) {
// Pseudocode. Ajoutez les casts qui vont bien.
BrochetteGen<FruitAPeler> brochette_p = new BrochetteGen<FruitAPeler>();
BrochetteGen<Fruit> brochette_f = brochette_p;
brochette_f.embrocher(fruit);
brochette_p.debrocher().peler();
}
- Invoquez
brochette_fp_fruit
avec une banane puis une fraise. - Implémentez
brochette_fp_fruit
dans les 4 langages Java, C++, C# et Nit
Questions
Chacun de ces 4 langages a une sémantique différente par rapport au sous-typage et à la généricité.
- Quels langages nécessitent de faire un cast pour pouvoir compiler statiquement ?
- Quels langages génèrent une erreur à l’exécution même quand on exécute avec une banane ?
- Où sont levés les erreurs dans le cas des fraises ? Chaque erreur ressemble-t-elle au cas de la fraise dans la recette 1 ou au cas de la fraise dans la recette 2 (ou à aucun de ces deux cas) ?
Recette 4 - brochette variée
On continue maintenant de regarder les problèmes de sous-typage des brochettes par rapport aux variations génériques. Pour ce faire, développe des fonction (méthodes/sous-programmes) utilitaires qui acceptent des brochettes passées en paramètre.
Réalisez ces fonctions dans les langages Java, C++, C# et Nit en utilisant en paramètre des BrochetteGen
(des types génériques).
Réalisez l’équivalent en Python (ou Ruby) en utilisant Brochette
la version non générique de la classe.
- Si besoin, en Java, utilisez les wildcards pour faire de la covariance et contravariance: voir
?
,extends
etsuper
. - Si besoin, en C#, créez des super interfaces covariante et contravariante à
BrochetteGen
: voirin
etout
. - En Nit, C++ et Python: débrouillez-vous.
Compte Sloubi
Créez une fonction (méthode statique ou fonction hors classe) compte_sloubifuit
:
- qui prend en paramètre une brochette (à vous de déterminer le type statique) ;
- débroche chacun des fruits ;
- compte combien il y avait de fruits et affiche la valeur à l’écran.
- On veut pouvoir appeler cette fonction autant avec une
BrochetteGen<Fruit>
qu’avec uneBrochetteGen<Banane>
.
Quel est le “meilleur” type statique du paramètre de la fonction compte_sloubifuit
pour le langage considéré ?
// idéalement on veut pouvoir écrire un truc du genre:
BrochetteGen<Fruit> bf = new BrochetteGen<Fruit>();
// embrochage de fruits variés dans bf.
compte_sloubifuit(bf);
// mais aussi quelque chose du genre
BrochetteGen<FruitAPeler> bp = new BrochetteGen<FruitAPeler>();
// embrochage de fruits variés dans bp.
compte_sloubifuit(bp);
Pêle-Mêle
Créez une fonction (méthode statique ou fonction hors classe) pele_mele
:
- qui prend en paramètre une brochette (à vous de déterminer le type statique)
- pèle tous les fruits de la brochette
- contrainte: implémentez cette fonction sans utiliser de casts
- vous ne pouvez pas changer les classes de fruits existantes
- on veut pouvoir appeler cette fonction autant avec une
BrochetteGen<FruitAPeler>
qu’avec uneBrochetteGen<Banane>
.
Quel est le “meilleur” type statique du paramètre de la fonction ?
Peut-on vraiment l’invoquer avec une BrochetteGen<Fruit>
? Si non, pourquoi ?
// On veut pouvoir écrire un truc du genre:
BrochetteGen<FruitAPeler> bp = new BrochetteGen<FruitAPeler>();
// embrochage de fruits variés dans bp.
pele_mele(bp);
// On aimerai aussi pouvoir écrire un truc du genre:
BrochetteGen<Fruit> bf = new BrochetteGen<Fruit>();
// embrochage de fruits variés dans bf.
pele_mele(bf);
Appel de fruits sauvages
Créez une fonction (méthode statique ou fonction hors classe) appel_a_peau
:
- qui prend en paramètre une brochette (à vous de déterminer le type statique)
- qui embroche une orange puis une banane
- contrainte: implémentez cette fonction sans utiliser de casts
- vous ne pouvez pas changer les classes de fruits existantes
- on veut pouvoir appeler cette fonction autant avec une
BrochetteGen<FruitAPeler>
qu’avec uneBrochetteGen<Fruit>
.
Quel est le type meilleur statique du paramètre de la fonction?
Peut-on l’invoquer avec une BrochetteGen<Banane>
? Si non pourquoi?
// idéalement on veut pouvoir écrire un truc du genre:
BrochetteGen<FruitAPeler> bp = new BrochetteGen<FruitAPeler>();
appel_a_peau(bp); // des fruits variés ont été embrochés.
// et quelque chose du genre
BrochetteGen<Fruit> bf = new BrochetteGen<Fruit>();
appel_a_peau(bf); // des fruits variés ont été embrochés.
Questions
Comparez les solutions de ces langages par rapport à
- La sûreté statique
- La verbosité
- La lourdeur en nombre d’entité du modèle
- La complexité du système de type sous-jacent
Travail à rendre
Une archive .zip
ou .tar.gz
contenant:
- Un répertoire par langage. Exemple
nit/
- Le code source dans le répertoire. Éventuellement dans un seul fichier si le langage le permet. Exemple
nit/brochette.nit
- Il n’y a pas de version différentes à faire à chaque étape : tout peut être mit dans le même projet.
- Les réponses aux questions doivent être indiquées dans le code source sous forme de commentaires longs.
Critères de qualité:
- Assurez-vous que le code soit petit, autonome et élégant (lisible et sans superflu)
- Autant que possible, minimisez les différences entre les langages et les variations (man
diff
) - Identifiez et justifiez et clairement les réponses aux questions