Modèle UML : Composition entre Jeu et Chambre
Partie 1 : Modélisation UML Question 1.1 - Type d'association entre Jeu et Chambre Analyse de la question : Le document source original contenant le diagramme UML du développeur est manquant dans l'énoncé fourni. Il est donc impossible d'affirmer avec une certitude absolue quel était le type d'association initialement dessiné par le développeur.
D'après le document Modèle UML : Composition entre Jeu et Chambre
Cet article a été rédigé automatiquement à partir du document source, puis vérifié avant publication.

Document source
Programming, Math, etc. · PDF · 2 pages
Afficher l'aperçu du document
Partie 1 : Modélisation UML
Question 1.1 - Type d'association entre Jeu et Chambre
Analyse de la question : Le document source original contenant le diagramme UML du développeur est manquant dans l'énoncé fourni. Il est donc impossible d'affirmer avec une certitude absolue quel était le type d'association initialement dessiné par le développeur.
Cependant, nous pouvons répondre à la partie théorique de la question.
Définition : Une association entre un ensemble (le tout) et ses éléments (les parties) est typiquement une relation d'agrégation ou de composition.
- La composition est une forme forte d'agrégation qui implique une dépendance de cycle de vie stricte : la partie n'existe que par l'existence du tout, et la destruction du tout entraîne la destruction de la partie. Une partie ne peut appartenir qu'à un seul composite à la fois.
Traduction en simple composition :
Pour modéliser que le Jeu est composé de Chambres (et qu'une chambre n'a de sens que dans un jeu donné), on dessine un trait continu entre la classe Jeu et la classe Chambre. Du côté de la classe Jeu, on place un losange plein (noir). La multiplicité du côté de Chambre serait typiquement 1..* (ou 0..* selon la taille de la grille) et du côté de Jeu, elle doit être strictement 1.
Question 1.2 - Pertinence de l'utilisation de la composition
L'énoncé fait référence à un modèle UML proposé par un développeur, qui n'a pas été extrait dans le texte source. Il est par conséquent impossible de juger les compositions spécifiques dessinées par ce développeur.
Explication théorique pour évaluer une composition : Pour donner raison ou tort à l'affirmation, il faut vérifier la règle de la dépendance de cycle de vie.
- Si le développeur a utilisé une composition entre
ChambreetOccupant(Soldat, Ennemi), c'est généralement incorrect. Un soldat ou un ennemi peut se déplacer d'une chambre à l'autre ; sa destruction n'est pas liée à la destruction d'une chambre spécifique. Il s'agirait plutôt d'une simple association ou d'une agrégation faible. - Si la composition est utilisée entre
JeuetChambre, c'est correct, car les chambres forment la grille de jeu et disparaissent avec lui.
Question 1.3 - Diagramme d'objets initial
La "première figure" de l'énoncé est manquante, ce qui empêche de connaître les positions exactes des entités sur la grille. Voici cependant comment construire le diagramme d'objets selon les données textuelles fournies.
Éléments à représenter :
- 1 instance de
Jeu(par exemplej1 : Jeu). - Plusieurs instances de
Chambre(par exemplec0_0 : Chambre,c0_1 : Chambre, etc., selon les coordonnées x,y). - 1 instance de
Soldat(par exemples1 : Soldat). - 3 instances de
Ennemi(par exemplee1 : Ennemi,e2 : Ennemi,e3 : Ennemi). - 3 instances de
Arme(par exemplea1 : Arme,a2 : Arme,a3 : Arme).
Liens :
Le jeu j1 est lié à toutes les instances de Chambre. Les objets Soldat, Ennemi et Arme sont liés aux objets Chambre correspondant à leurs coordonnées respectives (x pour les colonnes, y pour les lignes, avec 0,0 en haut à gauche).
Question 1.4 - Diagramme d'objets après actions du soldat
Comme pour la question précédente, l'absence des positions initiales nous oblige à décrire la structure logique des objets plutôt qu'à fournir un schéma spatial exact.
a) Situation avant les attaques :
- Le
Soldata récupéré un fusil : Il existe un lien direct entre l'instance du soldat (s : Soldat) et l'instance du fusil (f : Fusil). L'attributballesde l'objetfvaut 10. - Le soldat s'est déplacé : Le lien entre le soldat et son ancienne chambre est détruit. Un nouveau lien est créé entre
s : Soldatet une instancec_cible : Chambre. - La chambre cible : L'objet
c_cible : Chambrepossède également des liens avec deux instances d'ennemis de type Kattel (k1 : Kattel,k2 : Kattel).
b) Situation après les 5 attaques :
- L'arme a été utilisée : L'attribut
ballesde l'objetf : Fusilpasse de 10 à 5 (en supposant qu'une attaque consomme 1 balle, selon la logique standard). - La mise à jour des occupants se fait après l'attaque. L'état de santé ou la présence des objets
k1 : Katteletk2 : Katteldépendra des règles de dégâts (non fournies). Si les attaques sont mortelles, les liens entrec_cibleetk1,k2pourraient être détruits, et les objets supprimés.
Question 1.5 - Navigation des ennemis entre les chambres
Problème : Un ennemi doit pouvoir trouver les chambres contenant au moins un ennemi, sans ajouter de nouveau lien avec la classe Jeu (qui connaît toutes les chambres).
Solution proposée :
Il faut que la classe Chambre permette une navigation globale, ou de proche en proche.
- Option 1 (Attribut statique) : Ajouter un attribut de classe (statique) dans
Chambrequi référence toutes les instances créées. En UML, cela se note avec un attribut souligné, par exempletoutesLesChambres : Chambre [*]. - Option 2 (Auto-association) : Créer une association réflexive sur la classe
Chambrepour représenter la contiguïté (Nord, Sud, Est, Ouest). Un ennemi peut alors interroger sa chambre actuelle pour naviguer vers les chambres adjacentes, de proche en proche.
L'Option 1 est la plus directe pour répondre au besoin "naviguer vers les chambres qui contiennent au moins un ennemi" sans algorithme de recherche complexe sur un graphe.
Partie 2 : Codage C++
Question 2.1 - Implémentation des associations avec cardinalité multiple
a) Règles de gestion : D'après l'énoncé, un jeu contient plusieurs chambres. Une chambre peut contenir plusieurs ennemis et plusieurs armes. De plus, un occupant (comme le soldat) peut potentiellement interagir avec plusieurs armes ou ennemis. L'énoncé précise le besoin d'avoir "deux collections distinctes une pour les ennemis et une autre pour les armes".
b) Choix des structures STL :
Nous utiliserons le conteneur std::vector de la bibliothèque standard (STL).
- Pour les ennemis :
std::vector<Ennemi*> ennemis; - Pour les armes :
std::vector<Arme*> armes;
Justification : L'utilisation de pointeurs (*) est obligatoire en C++ pour profiter du polymorphisme (stocker des instances de Kattel ou Balbaz dans une collection d'Ennemi). Le std::vector garantit un accès rapide (en temps constant) et une itération aisée, ce qui est idéal pour parcourir les occupants d'une chambre.
Question 2.2 - Classe Chambre
Voici la déclaration et l'implémentation de la classe Chambre, intégrant les vecteurs et des méthodes de base pour ajouter et retirer des éléments.
#include <iostream>
#include <vector>
#include <algorithm>
class Ennemi; // Déclaration anticipée
class Arme; // Déclaration anticipée
class Chambre {
private:
std::vector<Ennemi*> ennemis;
std::vector<Arme*> armes;
// Attribut statique pour répondre à la question 1.5
static std::vector<Chambre*> toutesLesChambres;
public:
Chambre() {
toutesLesChambres.push_back(this);
}
~Chambre() {
// Nettoyage de la liste statique (simplifié)
toutesLesChambres.erase(std::remove(toutesLesChambres.begin(), toutesLesChambres.end(), this), toutesLesChambres.end());
}
void ajouterEnnemi(Ennemi* e) {
ennemis.push_back(e);
}
void retirerEnnemi(Ennemi* e) {
ennemis.erase(std::remove(ennemis.begin(), ennemis.end(), e), ennemis.end());
}
void ajouterArme(Arme* a) {
armes.push_back(a);
}
void retirerArme(Arme* a) {
armes.erase(std::remove(armes.begin(), armes.end(), a), armes.end());
}
// Accesseurs pour la logique du jeu
const std::vector<Ennemi*>& getEnnemis() const { return ennemis; }
const std::vector<Arme*>& getArmes() const { return armes; }
static const std::vector<Chambre*>& getToutesLesChambres() { return toutesLesChambres; }
};
// Initialisation de l'attribut statique
std::vector<Chambre*> Chambre::toutesLesChambres;
Question 2.3 - Correction du polymorphisme et attribut {frozen}
a) Pourquoi la partie de code ne fonctionne pas :
Dans le code fourni, la méthode get_type() est définie dans la classe de base Arme et dans la classe de base Ennemi, mais elle n'est pas déclarée comme virtuelle (mot-clé virtual).
Puisque les variables a et e sont des pointeurs sur les classes de base (Arme* et Ennemi*), l'appel a->get_type() appellera toujours la méthode de la classe de base, renvoyant "Arme", quel que soit l'objet réel pointé (Fusil ou Bombe). Le typage est résolu statiquement à la compilation (liaison précoce). Les conditions des if ne seront donc jamais vérifiées.
b) Constructeurs avec attribut {frozen} :
En UML, un attribut {frozen} signifie qu'il est en lecture seule après son initialisation (équivalent à const en C++). Puisqu'on ne redéfinit plus get_type(), c'est la classe de base Occupant (et Arme) qui stocke cet attribut et le renvoie.
#include <string>
// --- Hiérarchie des Armes ---
class Arme {
private:
const std::string type; // {frozen}
public:
Arme(std::string t = "Arme") : type(t) {}
std::string get_type() const { return type; }
virtual ~Arme() {}
};
class Fusil : public Arme {
public:
Fusil() : Arme("Fusil") {}
};
class Bombe : public Arme {
public:
Bombe() : Arme("Bombe") {}
};
// --- Hiérarchie des Occupants ---
class Occupant {
private:
const std::string type; // {frozen}
public:
Occupant(std::string t) : type(t) {}
std::string get_type() const { return type; }
virtual ~Occupant() {}
};
class Ennemi : public Occupant {
public:
Ennemi(std::string t = "Ennemi") : Occupant(t) {}
virtual void attaquer() { std::cout << "Enm - attaque\n"; }
virtual void fuir() { std::cout << "Enm - fuit\n"; }
};
class Kattel : public Ennemi {
public:
Kattel() : Ennemi("Kattel") {}
void attaquer() override { std::cout << "Ktl attaque\n"; }
void fuir() override { std::cout << "Ktl - fuit\n"; }
};
class Balbaz : public Ennemi {
public:
Balbaz() : Ennemi("Balbaz") {}
void attaquer() override { std::cout << "Blz - attaque\n"; }
void fuir() override { std::cout << "Blz - fuit\n"; }
};
Question 2.4 - Méthode reagir() et polymorphisme
Pour éviter les séries de if / else if sur les chaînes de caractères (qui est une mauvaise pratique orientée objet), nous allons implémenter reagir(Arme* a). Les comportements de fuite de Balbaz et Kattel nécessitent de parcourir les chambres.
class Ennemi : public Occupant {
public:
Ennemi(std::string t = "Ennemi") : Occupant(t) {}
virtual void attaquer() { std::cout << "Enm - attaque\n"; }
virtual void fuir() { std::cout << "Enm - fuit\n"; }
// Déclaration de la nouvelle méthode virtuelle pure
virtual void reagir(Arme* a) = 0;
};
class Kattel : public Ennemi {
public:
Kattel() : Ennemi("Kattel") {}
void attaquer() override { std::cout << "Ktl attaque\n"; }
void fuir() override {
// La fuite d'un Kattel se fait dans la chambre la moins peuplée d'ennemis
const auto& chambres = Chambre::getToutesLesChambres();
Chambre* chambreDest = nullptr;
size_t minEnnemis = (size_t)-1;
for (Chambre* c : chambres) {
size_t nbEnnemis = c->getEnnemis().size();
if (nbEnnemis < minEnnemis) {
minEnnemis = nbEnnemis;
chambreDest = c;
}
}
if (chambreDest) {
std::cout << "Kattel fuit vers la chambre ayant " << minEnnemis << " ennemis.\n";
// Logique de déplacement à intégrer ici
}
}
void reagir(Arme* a) override {
if (a->get_type() == "Fusil") {
attaquer();
} else if (a->get_type() == "Bombe") {
fuir();
}
}
};
class Balbaz : public Ennemi {
public:
Balbaz() : Ennemi("Balbaz") {}
void attaquer() override { std::cout << "Blz - attaque\n"; }
void fuir() override {
// La fuite d'un Balbaz se fait dans la chambre qui contient le max de balles
// Note : L'attribut 'balles' n'est pas déclaré dans l'énoncé de base pour Arme.
// On suppose ici qu'une méthode hypothétique get_balles() existerait sur Arme ou Fusil.
std::cout << "Balbaz cherche la chambre avec le maximum de balles pour fuir.\n";
// L'implémentation complète nécessiterait un cast dynamique vers Fusil pour compter les balles.
}
void reagir(Arme* a) override {
if (a->get_type() == "Fusil") {
fuir();
} else if (a->get_type() == "Bombe") {
attaquer();
}
}
};
Question 2.5 - Classe Jeu
La classe Jeu est responsable de la grille (matrice) et du constructeur qui place les éléments initiaux. Sans la configuration exacte de la "première figure", le constructeur inclut une logique générique de peuplement.
#include <vector>
class Jeu {
private:
int lignes;
int colonnes;
std::vector<std::vector<Chambre*>> grille;
public:
// Déclaration
Jeu(int l, int c);
~Jeu();
void afficher(); // Non implémentée (selon énoncé)
void gagnant(); // Non implémentée (selon énoncé)
void commanditer(std::string nom);
};
// Implémentation
Jeu::Jeu(int l, int c) : lignes(l), colonnes(c) {
grille.resize(lignes, std::vector<Chambre*>(colonnes, nullptr));
// Création des chambres
for (int i = 0; i < lignes; ++i) {
for (int j = 0; j < colonnes; ++j) {
grille[i][j] = new Chambre();
}
}
// Placement des occupants selon l'énoncé du jeu (logique simulée ici)
// 1 Soldat, 3 Ennemis, 3 Armes...
if (lignes > 0 && colonnes > 0) {
grille[0][0]->ajouterArme(new Fusil());
grille[0][1]->ajouterEnnemi(new Kattel());
grille[0][1]->ajouterEnnemi(new Balbaz());
}
}
Jeu::~Jeu() {
for (int i = 0; i < lignes; ++i) {
for (int j = 0; j < colonnes; ++j) {
delete grille[i][j];
}
}
}
Question 2.6 - Gestion de l'exception de débordement
L'énoncé indique que si le soldat sort de la grille, une exception est lancée au niveau de deplacer, capturée, et corrigée en inversant le mouvement. L'objet gérant la position semble s'appeler matrix dans le code source fourni, nous allons donc concevoir l'exception et un squelette pour l'objet qui gère le déplacement (potentiellement une classe GrilleManager ou le Soldat lui-même).
#include <exception>
enum sens { haut, bas, gauche, droite };
// Classe d'exception
class Debordement : public std::exception {
private:
sens direction;
public:
Debordement(sens d) : direction(d) {}
sens getDirectionInverse() const {
switch (direction) {
case haut: return bas;
case bas: return haut;
case gauche: return droite;
case droite: return gauche;
}
return haut; // par défaut
}
void corriger() {
std::cout << "Debordement détecté. Correction par déplacement dans le sens inverse.\n";
// La correction réelle nécessiterait un pointeur vers l'objet à déplacer
// ou renvoyer la direction inverse à l'appelant.
}
};
// Squelette de la classe simulant "matrix" mentionnée dans l'énoncé
class MatrixDeplacement {
public:
void deplacer(sens d, int n) {
// Logique fictive : on simule un débordement si n indique qu'on est au bord
bool debordement = false;
// Conditions fictives de bordures
// Si mouvement = haut et position_y == 0 -> debordement = true
if (debordement) {
throw Debordement(d);
} else {
std::cout << "Déplacement effectué avec succès.\n";
// Mise à jour réelle des coordonnées
}
}
};
Note sur le code source de la question 2.6 : Le bloc try-catch de l'énoncé utilise e.corriger(). Notre implémentation de Debordement fournit cette méthode. Pour que corriger() effectue réellement l'action opposée sur l'objet ciblé, l'exception devrait idéalement être construite en recevant une référence à cet objet (ex: throw Debordement(d, this)).
Méthode
Face à ce type de sujet mêlant analyse conceptuelle (UML) et implémentation concrète (C++), voici la démarche recommandée :
- Traçabilité UML vers Code : Gardez toujours à l'esprit que les choix d'architecture (associations, agrégations, cardinalités) vus en UML dictent la conception des classes. Une multiplicité
*en UML se traduit presque systématiquement par un conteneur standard en C++ (comme unstd::vector). - Maîtrise du polymorphisme : En C++, le typage dynamique et le polymorphisme exigent trois choses indissociables : des classes dérivées, des méthodes définies avec le mot-clé
virtualdans la classe de base, et une manipulation des objets au travers de pointeurs (*) ou de références (&). C'est le piège classique de la question 2.3. - Encapsulation et Cycle de vie : Lorsque vous créez des objets avec
newdans un constructeur (comme pour lesChambredansJeu), vous devez impérativement rédiger le destructeur correspondant (~Jeu()) contenant lesdeleteassociés pour éviter les fuites de mémoire. - Lecture attentive des règles métier : Dans les sujets à rallonge, les contraintes sont parfois dispersées. Les règles de "fuite" des ennemis (question 2.4) nécessitent un accès global ou relatif à d'autres objets, ce qui vous indique que votre conception initiale (question 1.5) doit rendre cette navigation techniquement possible (ex: liste statique des instances).
- Données manquantes : Lors d'un examen, si une figure annexe est manquante (comme ce fut le cas ici pour la question 1.3), énoncez clairement les hypothèses de base que vous posez pour pouvoir tout de même démontrer que vous maîtrisez la compétence évaluée (ici, tracer un diagramme d'objets logique).
Commentaires
Aucun commentaire pour le moment. Posez la première question.