TP 5 — Patrons de conception
Une fabrique Factory sur des fractions extends Number — avec une extension aux chaînes sans aucun if — puis un inventaire récursif du parc informatique, composite avant la lettre.
Objectif du TP : mettre en œuvre la version la plus simple du patron Factory pour créer des objets numériques de types variés, puis modéliser un inventaire de parc informatique où chaque élément peut en contenir d'autres — du Composite avant la lettre.
1. Fraction extends Number
Pour stocker n'importe quel élément numérique quel que soit son type, la structure s'appuie
sur Number : la classe-mère commune à tous les types numériques, la plus basse dans
la hiérarchie d'héritage. La classe Fraction (num, denom) en dérive et implémente
les quatre conversions exigées par la classe abstraite :
public class Fraction extends Number {
private int num;
private int denom;
public Fraction(int num, int denom) { /* ... */ }
@Override public int intValue() { /* ... */ }
@Override public long longValue() { /* ... */ }
@Override public float floatValue() { /* ... */ }
@Override public double doubleValue() { /* ... */ }
@Override public String toString() { /* ... */ }
}Une fois Fraction dérivée de Number, toute fraction est un Number et circule
partout où un Number est attendu.
2. NumberFactory, la fabrique
« Nous allons créer une "fabrique" basée sur la version la plus simple du patron de
conception Factory » : la fabrique instancie des objets de type Number à partir de
valeurs de différents types — ou de deux entiers pour une fraction :
public class NumberFactory {
public Number number(int value) { /* ... */ }
public Number number(long value) { /* ... */ }
public Number number(float value) { /* ... */ }
public Number number(double value) { /* ... */ }
public Number number(int num, int denom) { /* une Fraction */ }
}C'est la version légère du cours : une classe dont les méthodes décident du type concret créé, et retournent toujours le type de la classe mère.
3. Tabular et le scénario
La classe Tabular profite de la fabrique : elle stocke size éléments quelconques de
type Number — Tabular(int size) crée la structure, set(int i, Number number) affecte
l'emplacement i, max() identifie et retourne la valeur maximale, toString() affiche
le contenu. Pour comparer des Number hétérogènes — entiers, réels, fractions — le seul
langage commun est la vue double : la comparaison de max() passe naturellement par
doubleValue(). Q4 — écrivez un scénario qui mélange des fractions et d'autres
nombres dans un même Tabular et en affiche le maximum : la preuve que la fabrique et le
polymorphisme fonctionnent ensemble.
4. L'extension aux chaînes, sans aucun if
Q5 — étendez la fabrique pour qu'elle accepte aussi des chaînes de caractères, en
n'utilisant aucune structure conditionnelle : uniquement la gestion des exceptions, en
cascade. Chaque type candidat tente sa conversion dans son propre try ; l'échec
(NumberFormatException) fait passer au suivant :
public Number number(String s) {
try { return number(Integer.parseInt(s)); }
catch (NumberFormatException e) { /* pas un entier : on tente autre chose */ }
try { return number(Float.parseFloat(s)); }
catch (NumberFormatException e) { /* etc. pour les autres types */ }
// ... dernier essai, ou échec final
}Q6 — modifiez le scénario pour illustrer la nouvelle fonctionnalité. Q7 — commit : « tpQU-05::exo-pattern », puis poussez.
L'exception à la place du test
C'est le même esprit que le multi-catch du chapitre 4 : on tente, et c'est l'échec qui oriente le flot d'exécution. Lisibilité gagnée ; en contrepartie, lever une exception coûte cher — acceptable ici, à éviter dans une boucle serrée.
5. Le parc informatique en composite
Objectif : un inventaire des éléments qui composent les postes de travail d'un parc. La constitution modulaire des ordinateurs permet montage, échange, remplacement des composants ; l'inventaire requiert donc la mémorisation des assemblages. Trois types se partagent le travail :
Piece: un composant indivisible tel qu'acheté dans le commerce — il porte un numéro d'inventaire ;Element: un assemblage d'une ou plusieurs pièces ou éléments — la définition est récursive, unElementpeut donc en contenir d'autres ;Machine: l'ensemble des composants du poste de travail d'une personne.
Fonctionnalités attendues : créer un élément, en ajouter, en enlever ; associer un poste
de travail à un utilisateur ; identifier une pièce et connaître son numéro d'inventaire.
Le cœur du diagramme UML tient en une ligne : l'attribut #components de Element est
une agrégation 0..* vers Piece et Element — l'auto-référence qui fait tout le
patron. La description du parc devient une arborescence, et l'affichage du poste d'Alice
s'obtient par un simple parcours récursif : chaque niveau s'affiche lui-même puis délègue
à ses composants.
Poste de Alice:
écran(N.1340)
clavier(N.1934)
souris(N.2805)
unité centrale:
carte mère(N.1569)
extension mémoire(N.289)
disque scsi(N.299)
carte graphique:
extension mémoire(N.2879)
processeur graphique(N.7289)
webcam(N.2222)Notez l'imbrication : la « carte graphique » est elle-même un Element dans l'« unité
centrale ». Q1 — écrivez Element, Machine et Piece ; Q2 — écrivez la classe
UseStock réalisant le scénario qui mène à l'affichage ci-dessus ; Q3 — commit :
« tpQU-05::exo-machine », puis poussez.
Reconnaître un Composite
Dès qu'un objet peut contenir des objets de son propre type — fichiers dans des
répertoires, sous-menus dans des menus, articles dans des paniers — le Composite est
candidat. Ici, Element contient des Element : le parcours récursif remplace une
cascade de tests de types.
Parce que le but est de stocker n'importe quel élément numérique — entiers, réels,
fractions — dans une même structure. Number est le plus petit dénominateur commun :
un tableau de Number accepte tout, et doubleValue() offre la comparaison uniforme.
Le client ne choisit pas la classe concrète à instancier : il demande « un nombre à
partir de ces données » et reçoit un objet typé Number, dont le type réel est
décidé par la fabrique. La création est centralisée, le type concret repoussé à
l'exécution.
Chaque tentative de conversion (Integer.parseInt, puis les suivants) réussit ou
lève une NumberFormatException. Le catch joue le rôle du test : échouer fait
passer au type suivant. Le flot d'exécution est porté par les exceptions, pas par des
conditions.
Piece est une feuille : indivisible, juste un numéro d'inventaire. Element est un
nœud : il assemble des pièces ou d'autres éléments. Machine est la racine du poste
: l'ensemble des composants d'un utilisateur. Trois rôles dans la même arborescence.
tpQU_05.pdf
R2.03 · TP 5 — Patrons de conception · 2 pages
Chargement du document…
Dernière mise à jour le