• 12 heures
  • {0} Facile|{1} Moyenne|{2} Difficile

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Mis à jour le 30/04/2014

Les classes (2nde partie)

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Dans le chapitre précédent, nous avons présenté les bases de la théorie des classes.
Nous allons maintenant introduire des notions complémentaires qui vous permettront de structurer votre code encore plus facilement et proprement. Contrairement à la plupart des notions de cette partie sur la POO, celles que nous allons découvrir dans ce chapitre n'ont pas été présentées dans le premier chapitre et sont donc complètement nouvelles.
Redoublez donc d'attention !

Les éléments statiques

Peut-être que certains se rappellent de la classe Math que nous avons déjà utilisé. Ils se demandent alors pourquoi nous l'avions utilisé en employant directement le nom de la classe et sans passer par une instance de celle-ci :

var monNombre:Number = Math.sqrt(2);

Comment cela est-ce possible ?

Maintenant que vous avez déjà quelques connaissances en POO, il est tout à fait justifié de se poser ce genre de question. En fait, la classe Math utilise des éléments qui sont un peu particuliers : les éléments statiques !
Comme nous le verrons, ces éléments statiques ne sont pas définis pour les instances d'une classe, mais pour la classe elle-même. Il existe deux types d'éléments statiques qui sont :

  • Les variables statiques.

  • Les méthodes statiques.

Nous verrons donc comment créer ces éléments et leur intérêt.

Les variables statiques

Les variables statiques sont déclarées à l'aide du mot-cléstatic, et sont associées, donc définies, pour la classe.
Prenons l'exemple de notre classe Voiture du chapitre précédent et ajoutons-y une variable statique représentant le nombre de fois où celle-ci a été instanciée :

public static var occurences:int = 0;

Cette variable est donc partagée par la classe, elle n'appartient pas aux instances de celle-ci. Toutefois, cette variable est accessible depuis n'importe quel point de la classe. Nous pourrions notamment incrémenter cette variable à l'intérieur du constructeur de la classe Voiture afin de comptabiliser le nombre d'occurrences de celle-ci :

occurrences++;

Grâce à cette variable statique, nous pourrions obtenir le nombre d'instances de la classe Voiture, n'importe où dans le code. Pour cela, nul besoin de créer une nouvelle instance de la classe, il suffit d'utiliser le nom de la classe lui-même :

var uneRenault:Voiture = new Voiture("Renault");
var unePeugeot:Voiture = new Voiture("Peugeot");
var uneCitroen:Voiture = new Voiture("Citroën");
trace(Voiture.occurrences); // Affiche : 3

Les méthodes statiques

Il existe un second type d'éléments statiques : il s'agit des méthodes statiques.
Dans le chapitre précédent, je vous avais dit que les méthodes servaient principalement à la lecture ou à la modification d'un attribut. Nous pouvons donc introduire les méthodes statiques comme l'ensemble des méthodes qui offrent des fonctionnalités n'affectant pas au moins l'un des attributs d'une classe.

Ces éléments statiques sont également déclarés à l'aide du mot-clé static :

public static function uneMethode():void
{
    // Instructions
}

À l'aide de ces méthodes statiques, il nous est possible de recréer la classe Math, que nous pourrions renommer MaClasseMath. Voici par exemple la redéfinition de la méthode pow() en puissance() :

public static function puissance(nombre:int, exposant:int):int
{
    var resultat:int = nombre
    for(var i:int = 1; i < exposant; i++)
    {
        resultat *= nombre;
    }
    return resultat;
}

Le code complet de la classe serait :

package
{
    public class MaClasseMath
    {
        public static function puissance(nombre:int, exposant:int):int
        {
            var resultat:int = nombre
            for(var i:int = 1; i < exposant; i++)
            {
                resultat *= nombre;
            }
            return resultat;
        }
    }
}

Nous pouvons ainsi l'utiliser sans créer d'occurrences de cette nouvelle classe :

var monNombre:int = MaClasseMath.puissance(2,10);
trace(monNombre); // Affiche : 1024

Des classes telles que Math ont été conçues pour être utilisées uniquement grâce à des éléments statiques. En utilisant ce principe, vous pouvez ainsi regrouper un ensemble de fonctionnalités à l'intérieur d'une même classe. Vous n'aurez donc pas besoin de créer d'occurrences de celle-ci et vous ferez ainsi l'économie des instructions de déclarations et d'initialisations des instances.

Une nouvelle sorte de « variable » : la constante !

Lorsque nous avons introduit les variables dans la première partie, nous n'avons pas parlé des constantes !
Comme son nom l'indique, la valeur d'une constante est figée contrairement à celle d'une variable qui est vouée à évoluer au cours du programme. Ces constantes sont principalement utilisées en POO et représentent des caractéristiques constantes d'un objet.

Je vous invite à découvrir ce nouveau type d'élément sans plus attendre !

Présentation

Déclaration

De la même façon que nous avions l'instruction ou mot-clé var pour déclarer une variable, nous disposons du mot-clé const en ce qui concerne les constantes. Comme les variables, ces dernières possèdent également un type.
Voici par exemple la déclaration d'une constante de type String :

const MA_CONSTANTE:String;

Le code précédent n'a malheureusement aucun intérêt et ne sert à rien sans l'initialisation de la constante !

Initialisation

Tout comme une variable, il est important d'initialiser une constante. Vous pouvez procéder exactement de la même manière que pour une variable. La technique d'initialisation dépend bien entendu du type de la constante.

Voici donc comment initialiser notre constante précédente de type String :

const MA_CONSTANTE:String = "Valeur";

Intérêt des constantes

Il y a certainement plus de la moitié, voire même les trois quarts d'entre vous qui se sont posé la question suivante :

À quoi ces constantes peuvent-elles bien servir ?

Contrairement à ce que vous pensez, les constantes ont plusieurs utilités.

  • Tout d'abord, elles permettent de mettre des noms sur des valeurs. Votre programme ne marchera pas mieux avec cela, c'est uniquement une question de clarification du code. Avouez qu'il est quand même plus aisé de comprendre la signification d'une expression si celle-ci utilise des noms plutôt que des valeurs : prixRoue * NOMBRE_DE_ROUES plutôt que prixRoue * 4.
    Dans le second cas, nous pourrions nous demander s'il s'agit d'une augmentation du prix d'une roue, une conversion du prix des euros aux dollars, ou bien une multiplication par le nombre de roues. Dans la première expression, l'opération est tout à fait claire ; ce qui simplifie grandement le travail de relecture d'un code.

  • Une autre utilité des constantes est de s'assurer de la pérennisation du code. Imaginez que le nombre de roues de votre voiture puisse servir à plusieurs calculs comme le prix de l'ensemble, son poids, etc. Vous devrez donc utiliser cette valeur plusieurs fois dans votre code et à des endroits différents. Ainsi, en utilisant une constante à la place de la valeur réelle, vous facilitez une éventuelle mise à jour de votre programme dans l'hypothèse de l'invention de la voiture à 6 roues !
    Essayez d'imaginer le travail qu'il aurait fallu fournir pour remplacer chacune des valeurs présentes dans des coins opposés de votre code.

Un objet dans un objet (dans un objet...)

Jusqu'à présent, nous n'avions utilisé qu'une seule classe à la fois. Mais là où la POO devient vraiment intéressante, c'est lorsque nous combinons les classes entre elles !

Le problème du pétrole

Reprenons la classe Voiture :

package
{
    public class Voiture
    {
        /*************** Attributs ***************/
        private var _marque:String;
        private var _couleur:String;
        private var _longueur:int;
        private var _immatriculation:String;
        
        /************* Constructeur *************/
        public function Voiture(marque:String)
        {
            _marque = marque;
            _couleur = "Sans couleur";
            _longueur = -1;
            _immatriculation = "Non immatriculée";
        }

        /*************** Accesseurs ***************/
        public function get marque():String
        {
            return _marque;
        }
        
        public function get couleur():String
        {
            return _couleur;
        }
        
        public function get longueur():int
        {
            return _longueur;
        }
        
        public function get immatriculation():String
        {
            return _immatriculation;
        }
        
        /*************** Mutateurs ***************/
        public function set couleur(nouvelleCouleur:String):void
        {
            _couleur = nouvelleCouleur;
        }
        
        public function set longueur(nouvelleLongueur:int):void
        {
            _longueur = (nouvelleLongueur > 0) ? nouvelleLongueur : -1;
        }
        
        public function set immatriculation(nouvelleImmatriculation:String):void
        {
            _immatriculation = nouvelleImmatriculation;
        }
        
    }
    
}

Nous voulons à présent que nos objets contiennent de l'essence. Pour cela, nous serions tenté de procéder ainsi :

package
{
    public class Voiture
    {
        /*************** Attributs ***************/
        private var _marque:String;
        private var _couleur:String;
        private var _longueur:int;
        private var _immatriculation:String;

        private var _typeEssence:String;
        private var _prixEssence:Number;
        private var _quantiteEssence:Number;
        
        /************* Constructeur *************/
        public function Voiture(marque:String)
        {
            _marque = marque;
            _couleur = "Sans couleur";
            _longueur = -1;
            _immatriculation = "Non immatriculée";
            _typeEssence = "Sans Plomb";
            _prixEssence = 1.4; // Euros par litre
            _quantiteEssence = 10; // Litres
        }

        /*************** Accesseurs ***************/
        public function get marque():String
        {
            return _marque;
        }
        
        public function get couleur():String
        {
            return _couleur;
        }
        
        public function get longueur():int
        {
            return _longueur;
        }
        
        public function get immatriculation():String
        {
            return _immatriculation;
        }

        public function get typeEssence():String
        {
            return _typeEssence;
        }
        
        public function get prixEssence():Number
        {
            return _prixEssence;
        }
        
        public function get quantiteEssence():Number
        {
            return _quantiteEssence;
        }
        
        /*************** Mutateurs ***************/
        public function set couleur(nouvelleCouleur:String):void
        {
            _couleur = nouvelleCouleur;
        }
        
        public function set longueur(nouvelleLongueur:int):void
        {
            _longueur = (nouvelleLongueur > 0) ? nouvelleLongueur : -1;
        }
        
        public function set immatriculation(nouvelleImmatriculation:String):void
        {
            _immatriculation = nouvelleImmatriculation;
        }

        public function set typeEssence(nouveauType:String):void
        {
            _typeEssence = nouveauType;
        }
        
        public function set prixEssence(nouveauPrix:Number):void
        {
            _prixEssence = nouveauPrix;
        }
        
        public function set quantiteEssence(nouvelleQuantite:Number):void
        {
            _quantiteEssence = nouvelleQuantite;
        }
        
    }
    
}

Notre classe commence à devenir compliquée, il vaudrait mieux créer une nouvelle classe pour partager les propriétés.

Une nouvelle classe

Créons une classe Essence à mettre dans le fichier Essence.as :

package
{
    public class Essence
    {
        /*************** Attributs ***************/
        private var _type:String;
        private var _prix:Number;
        private var _quantite:Number;
        
        /************* Constructeur *************/
        public function Essence()
        {
            _type = "Sans Plomb";
            _prix = 1.4; // Euros par litre
            _quantite = 10; // Litres
        }

        /*************** Accesseurs ***************/
        public function get type():String
        {
            return _type;
        }
        
        public function get prix():Number
        {
            return _prix;
        }
        
        public function get quantite():Number
        {
            return _quantite;
        }
        
        /*************** Mutateurs ***************/
        public function set type(nouveauType:String):void
        {
            _type = nouveauType;
        }
        
        public function set prix(nouveauPrix:Number):void
        {
            _prix = nouveauPrix;
        }
        
        public function set quantite(nouvelleQuantite:Number):void
        {
            _quantite = nouvelleQuantite;
        }
        
    }
    
}

Nous transférons donc toutes les propriétés relatives à l'essence de la voiture dans la nouvelle classe. Il va falloir maintenant adapter la classe Voiture :

package
{
    public class Voiture
    {
        /*************** Attributs ***************/
        private var _marque:String;
        private var _couleur:String;
        private var _longueur:int;
        private var _immatriculation:String;

        private var _carburant:Essence; // Nouvel attribut pointant sur un objet de classe Essence !
        
        /************* Constructeur *************/
        public function Voiture(marque:String)
        {
            _marque = marque;
            _couleur = "Sans couleur";
            _longueur = -1;
            _immatriculation = "Non immatriculée";
            _carburant = new Essence(); // Nous créons un objet Essence par défaut dans le constructeur
        }

        /*************** Accesseurs ***************/
        public function get marque():String
        {
            return _marque;
        }
        
        public function get couleur():String
        {
            return _couleur;
        }
        
        public function get longueur():int
        {
            return _longueur;
        }
        
        public function get immatriculation():String
        {
            return _immatriculation;
        }

        public function get carburant():Essence // Nouvel accesseur, renvoyant un objet de classe Essence
        {
            return _carburant;
        }

        /*************** Mutateurs ***************/
        public function set couleur(nouvelleCouleur:String):void
        {
            _couleur = nouvelleCouleur;
        }
        
        public function set longueur(nouvelleLongueur:int):void
        {
            _longueur = (nouvelleLongueur > 0) ? nouvelleLongueur : -1;
        }
        
        public function set immatriculation(nouvelleImmatriculation:String):void
        {
            _immatriculation = nouvelleImmatriculation;
        }

        public function set carburant(nouveauCarburant:Essence):void // Nouveau mutateur, affectant un objet de classe Essence
        {
            _carburant = nouveauCarburant;
        }
    }
    
}

Comme vous pouvez le constater, nous pouvons écrire des attributs pointant sur des objets. Nous pourrions même mettre un attribut de type Voiture !

private var ancienneVoiture:Voiture; // L'ancienne voiture du propriétaire

Pour modifier le carburant de notre voiture, il faut procéder ainsi :

var maVoiture = new Voiture("Peugeot");
maVoiture.carburant.type = "Diesel";
trace("Type de carburant : " + maVoiture.carburant.type); // Affiche : Type de carburant : Diesel

Vous remarquerez que nous procédons de la même façon que pour toutes les propriétés, en utilisant le caractère point « . », comme nous l'avons vu dans le premier chapitre cette partie. Il suffit donc de mettre un point à chaque fois que nous voulons accéder à la propriété d'un objet :

  • Une première fois lorsque nous voulons accéder à la propriété carburant de notre objet maVoiture.

  • Une seconde fois lorsque nous voulons modifier le type du carburant de la voiture.

Pour résumer la situation, je vous propose un petit schéma UML des classes Voiture et Essence que nous venons de créer :

Les classes Voiture et Essence
Les classes Voiture et Essence

Exercice : Jeu de rôle

Présentation de l'exercice

Le combat final contre le grand Méchant approche ! Votre personnage, son épée légendaire au poing, se dresse devant cet immense monstre armé jusqu'aux dents ! :pirate:

Le moment est venu de passer à la pratique ! :)
Je propose la réalisation d'un petit programme ressemblant à un jeu de rôle afin de bien revoir les notions essentielles du chapitre.

L'objectif de cet exercice est de créer la (ou les) classe(s) nécessaires au bon fonctionnement du programme principal (que nous adapterons si besoin). Voici le déroulement de ce programme :

  • Nous créons un objet représentant votre personnage, puis nous l'armons.

  • Nous créons de façon similaire l'objet représentant le Méchant.

  • Le Méchant attaque une fois votre personnage.

  • Votre personnage riposte et attaque une fois le Méchant.

Pour apporter un peu de piment à ce programme, les personnages peuvent succomber aux attaques, et leur arme peut infliger un coup critique (elle a des chances d'infliger le double de dégâts à l'adversaire).

Nous allons passer par trois étapes successives, que j'ai nommées solutions, pour voir comment programmer correctement en Orienté Objet pas à pas : à chaque étape, nous améliorerons notre code.

Solution initiale

Création de la classe

Commençons par créer notre première classe : j'ai choisi de l'appeler Personnage. En effet, ce sera la classe des objets représentant nos deux personnages (vous et le Méchant).
Dans un nouveau fichier, appelé Personnage.as, écrivons la structure de base de notre classe : le package, la classe et le constructeur :

package
{
    
    public class Personnage
    {
        
        // Constructeur
        public function Personnage()
        {
            
        }
    }
}
Les attributs

Ensuite, ajoutons les attributs de la classe. Pour cela, réfléchissons aux données utiles pour notre combat.
Comme dans tous les jeux avec des combats (ou presque), donnons un niveau de santé à nos personnages, que nous initialiserons à 100. Et pour les armer, indiquons la puissance de l'attaque qu'ils vont porter à leur adversaire ainsi que les chances de coup critique :

// Santé du personnage
private var _sante:int;

// Dégâts de base
private var _degats:int;

// Chances de faire une critique (sur 100)
private var _chanceCritique:int;
Les accesseurs

N'oublions pas d'accompagner les attributs de leurs accesseurs :

public function get sante():int
{
	return _sante;
}

public function set sante(value:int):void
{
	_sante = value;
}

public function get degats():int
{
	return _degats;
}

public function set degats(value:int):void
{
	_degats = value;
}

public function get chanceCritique():int
{
	return _chanceCritique;
}

public function set chanceCritique(value:int):void
{
	_chanceCritique = value;
}
Le contructeur

Ensuite, initialisons nos attributs au sein du constructeur :

// Constructeur
public function Personnage()
{
	sante = 100;
	degats = 0;
	chanceCritique = 0;
}
La méthode

Enfin, ajoutons une méthode, afin que nos personnages puissent attaquer un autre personnage :

public function attaquer(cible:Personnage):void
{
	var degatsAppliques:int = degats;
	
	// On jette un dé à 100 faces : si le résultat est inférieur ou égal à la chance de coup critique, l'attaque fait un coup critique !
	if (Math.random() * 100 <= chanceCritique)
	{
		trace("Critique !");
		// On double les dégâts !
		degatsAppliques *= 2;
	}
	
	// On applique les dégâts
	cible.sante -= degatsAppliques;
	
	if (cible.sante <= 0)
	{
		trace("La cible est décédée.");
	}
	else
	{
		trace("Il reste " + cible.sante + " PV à la cible.");
	}
}

Comme vous pouvez le constater, nous passons en paramètre un objet de la classe Personnage, afin de rendre le code logique et surtout très lisible. Ainsi, pour qu'un personnage attaque un second, il faudra procéder ainsi :

personnageA.attaquer(personnageB); // Le personnageA attaque le personnageB ! S'en est fini de lui !
La classe complète

Si tout se passe bien, vous devriez normalement avoir une classe Personnage qui correspond à la description ci-dessous :

La classe Personnage
La classe Personnage

Voici le code complet de notre classe Personnage, pour vérifier le vôtre :

package
{
    
    public class Personnage
    {
        
        // Santé du personnage
        private var _sante:int;
        
        // Dégâts de base
        private var _degats:int;
        
        // Chances de faire une critique (sur 100)
        private var _chanceCritique:int;
        
        public function Personnage()
        {
            sante = 100;
            degats = 0;
            chanceCritique = 0;
        }
        
        public function get sante():int
        {
            return _sante;
        }
        
        public function set sante(value:int):void
        {
            _sante = value;
        }
        
        public function get degats():int
        {
            return _degats;
        }
        
        public function set degats(value:int):void
        {
            _degats = value;
        }
        
        public function get chanceCritique():int
        {
            return _chanceCritique;
        }
        
        public function set chanceCritique(value:int):void
        {
            _chanceCritique = value;
        }
        
        public function attaquer(cible:Personnage):void
        {
            var degatsAppliques:int = degats;
            
            // On jette un dé à 100 faces : si le résultat est inférieur ou égal à la chance de coup critique, l'attaque fait un coup critique !
            if (Math.random() * 100 <= chanceCritique)
            {
                trace("Critique !");
                // On double les dégâts !
                degatsAppliques *= 2;
            }
            
            // On applique les dégâts
            cible.sante -= degatsAppliques;
            
            if (cible.sante <= 0)
            {
                trace("La cible est décédée.");
            }
            else
            {
                trace("Il reste " + cible.sante + " PV à la cible.");
            }
        }
    }
}
Le programme principal

Votre classe Main vide (contenue dans le fichier Main.as) devrait ressembler à cela :

package {
	import flash.display.Sprite;
	import flash.events.Event;
	
	public class Main extends Sprite {
		
		public function Main():void {
			if (stage)
				init();
			else
				addEventListener(Event.ADDED_TO_STAGE, init);
		}
		
		private function init(e:Event = null):void {
			removeEventListener(Event.ADDED_TO_STAGE, init);
			// entry point
			
		}
	}
}

Commençons par déclarer la variable qui pointera vers le premier objet de classe Personnage (celui qui vous représente) :

var moi:Personnage = new Personnage();

Ensuite, donnons-lui son épée légendaire (elle fait 80 dégâts de base et a 80 chances sur 100 de faire un coup critique) :

moi.degats = 80;
moi.chanceCritique = 80;

Le code pour créer le Méchant est très similaire :

var mechant:Personnage = new Personnage();
mechant.degats = 40;
mechant.chanceCritique = 10;

Enfin, simulons le combat épique qui a lieu entre nos deux personnages ! Si vous vous souvenez de ma remarque sur la méthode attaquer() un peu plus haut, vous savez comment procéder :

trace("Le méchant m'attaque ! ");
mechant.attaquer(moi);

trace("Il va connaître ma fureur ! A l'attaque !");
moi.attaquer(mechant);

Voici le code complet de notre classe Main :

package
{
	import flash.display.Sprite;
	import flash.events.Event;
	
	public class Main extends Sprite
	{
		
		public function Main():void
		{
			if (stage)
				init();
			else
				addEventListener(Event.ADDED_TO_STAGE, init);
		}
		
		private function init(e:Event = null):void
		{
			removeEventListener(Event.ADDED_TO_STAGE, init);
			// entry point
			
			// Création du personnage vous représentant
			var moi:Personnage = new Personnage();
			moi.degats = 80;
			moi.chanceCritique = 80;
			
			// Création du personnage Méchant
			var mechant:Personnage = new Personnage();
			mechant.degats = 40;
			mechant.chanceCritique = 10;
			
			// Simulation du combat
			trace("Le méchant m'attaque ! ");
			mechant.attaquer(moi);
			trace("Il va connaître ma fureur ! A l'attaque !");
			moi.attaquer(mechant);
		}
	}
}
Résultat

Nous pouvons maintenant compiler et tester le projet. Voici ce que donne la console :

Le méchant m'attaque ! 
Critique !
Il reste 20 PV à la cible.
Il va connaître ma fureur ! A l'attaque !
Critique !
La cible est décédée.

Gagné !

Attendez malheureux ! Ne criez pas victoire trop vite ! En effet, notre classe pourrait être améliorée...
Vous ne voyez pas en quoi ? Et bien, pensez au chapitre précédent : «Un objet dans un objet (dans un objet...)». Maintenant, réfléchissez à cette problématique : comment pourrait-on mieux séparer les données et les propriétés de ma classe Personnage ? En créant de nouvelles classes, pardi ! :D

Une nouvelle classe

En effet, il serait judicieux de représenter les armes que portent nos personnages par des objets à part entière : cela semble logique, et cela respecte les principes de la POO. En outre, cela nous faciliterait énormément la tâche si nous devions gérer un inventaire par exemple : nous pourrions mettre autant d'objets que l'on veut, et équiper nos personnages avec, tout ceci de façon très souple et naturelle !

La classe Arme

L'idée est donc de transférer toutes les propriétés relatives aux armes dans une nouvelle classe Arme, comme ceci :

Transfert des propriétés dans la classe Arme
Transfert des propriétés dans la classe Arme

Il nous faudra donc créer une nouvelle classe (ici dans le fichier Arme.as) :

package
{
    
    public class Arme
    {
        
        // Dégâts de l'arme
        private var _degats:int;
        
        // Chances de faire un coup critique (sur 100)
        private var _chanceCritique:int;
        
        public function Arme()
        {
            degats = 0;
            chanceCritique = 0;
        }
        
        public function get chanceCritique():int
        {
            return _chanceCritique;
        }
        
        public function set chanceCritique(value:int):void
        {
            _chanceCritique = value;
        }
        
        public function get degats():int
        {
            return _degats;
        }
        
        public function set degats(value:int):void
        {
            _degats = value;
        }
    }
}
La classe Personnage

N'oublions pas d'adapter la classe Personnage, comme nous l'avons fait dans le chapitre précédent :

package
{
    
    public class Personnage
    {
        
        // Santé du personnage
        private var _sante:int;
        
        // Arme équipée
        private var _armeEquipee:Arme; // Nouvel attribut pointant sur l'arme équipée
        
        public function Personnage()
        {
            sante = 100;
        }
        
        public function get sante():int
        {
            return _sante;
        }
        
        public function set sante(value:int):void
        {
            _sante = value;
        }
        
        public function get armeEquipee():Arme // Nouvel accesseur
        {
            return _armeEquipee;
        }
        
        public function set armeEquipee(value:Arme):void // Nouveau mutateur
        {
            _armeEquipee = value;
        }
        
        public function attaquer(cible:Personnage):void
        {
            // Au cas où aucun arme n'est équipée (l'objet armeEquipee est null)
            if (armeEquipee == null)
            {
                trace("Aucune arme équipée : l'attaque échoue.");
            }
            else
            {
                var degatsAppliques:int = armeEquipee.degats; // Désormais, on utilise les propriétés de l'objet armeEquipee
                
                if (Math.random() * 100 <= armeEquipee.chanceCritique) // Ici aussi, on utilise les propriétés de l'objet armeEquipee
                {
                    trace("Critique !");
                    // On double les dégâts !
                    degatsAppliques *= 2;
                }
                
                // On applique les dégâts
                cible.sante -= degatsAppliques;
                
                if (cible.sante <= 0)
                {
                    trace("La cible est décédée.");
                }
                else
                {
                    trace("Il reste " + cible.sante + " PV à la cible.");
                }
            }
        }
    }
}
Le programme principal

Là aussi, il va falloir adapter un peu : au lieu d'affecter directement les dégâts et les chances de coup critique aux personnages, nous créons dans un premier temps les armes via des objets de classe Arme, pour ensuite les équiper aux personnages :

var epeeLegendaire:Arme = new Arme();
epeeLegendaire.degats = 80;
epeeLegendaire.chanceCritique = 50;

var hacheDeGuerre:Arme = new Arme();
hacheDeGuerre.degats = 40;
hacheDeGuerre.chanceCritique = 10;

var moi:Personnage = new Personnage();
moi.armeEquipee = epeeLegendaire;

var mechant:Personnage = new Personnage();
mechant.armeEquipee = hacheDeGuerre;

trace("Le méchant m'attaque ! ");
mechant.attaquer(moi);
trace("Il va connaître ma fureur ! A l'attaque !");
moi.attaquer(mechant);

Vous avouerez que ce code est quand même plus clair que le précédent ! :)

Résultat

Et voici le résultat à la console lorsque l'on teste le projet :

Le méchant m'attaque ! 
Il reste 60 PV à la cible.
Il va connaître ma fureur ! A l'attaque !
Critique !
La cible est décédée.

Malheureusement, notre code pose encore problème : il ne respecte pas bien le principe d'encapsulation. Si vous regardez bien la méthode attaquer(), nous utilisons des propriétés de la classe Arme et reproduisons son comportement (à savoir : les coups critiques) dans la classe Personnage : en toute logique, si une arme devrait faire un coup critique, nous devrions le déterminer dans la bonne classe, autrement dit la classe Arme ! ^^

La bonne solution

La bonne façon de procéder consiste donc à appliquer les dégâts qui vont être fait dans la classe Arme. Pour cela, créons dans cette classe une nouvelle méthode frapper() :

public function frapper(cible:Personnage):void
{
	var degatsAppliques:int = degats;
	
	// On jette un dé à 100 faces : si le résultat est inférieur ou égal à la chance de coup critique, l'attaque fait un coup critique !
	if (Math.random() * 100 <= chanceCritique)
	{
		trace("Critique !");
		// On double les dégâts !
		degatsAppliques *= 2;
	}
	
	// On applique les dégâts
	cible.sante -= degatsAppliques;
}

Il va donc falloir appeler cette nouvelle méthode dans la méthode attaquer() de la classe Personnage :

public function attaquer(cible:Personnage):void
{
	if (armeEquipee == null)
	{
		trace("Aucune arme équipée : l'attaque échoue.");
	}
	else
	{
		armeEquipee.frapper(cible); // Nous appelons la nouvelle méthode ici
		
		if (cible.sante <= 0)
		{
			trace("La cible est décédée.");
		}
		else
		{
			trace("Il reste " + cible.sante + " PV à la cible.");
		}
	}
}
Les classes

L'un des intérêts de l'utilisation de la représentation UML est justement de faciliter cette étape de conception et d'organisation des différentes classes d'un même programme. Cela permet de visualiser la structure d'un projet en ne faisant ressortir que les informations utiles et ainsi, programmer plus rapidement et de manière plus propre.

Au final, vos classes devraient ressembler à ceci :

Les classes Personnage et Arme
Les classes Personnage et Arme

Voici le code complet de nos classes :

package
{
    
    public class Personnage
    {
        
        // Santé du personnage
        private var _sante:int;
        
        // Amre équipée
        private var _armeEquipee:Arme;
        
        public function Personnage()
        {
            sante = 100;
        }
        
        public function get sante():int
        {
            return _sante;
        }
        
        public function set sante(value:int):void
        {
            _sante = value;
        }
        
        public function get armeEquipee():Arme
        {
            return _armeEquipee;
        }
        
        public function set armeEquipee(value:Arme):void
        {
            _armeEquipee = value;
        }
        
        public function attaquer(cible:Personnage):void
        {
            if (armeEquipee == null)
            {
                trace("Aucune arme équipée : l'attaque échoue.");
            }
            else
            {
                armeEquipee.frapper(cible);
                
                if (cible.sante <= 0)
                {
                    trace("La cible est décédée.");
                }
                else
                {
                    trace("Il reste " + cible.sante + " PV à la cible.");
                }
            }
        }
    }
}
package
{
    
    public class Arme
    {
        
        // Dégâts de l'arme
        private var _degats:int;
        
        // Chances de faire un coup critique (sur 100)
        private var _chanceCritique:int;
        
        public function Arme()
        {
            degats = 0;
            chanceCritique = 0;
        }
        
        public function get chanceCritique():int
        {
            return _chanceCritique;
        }
        
        public function set chanceCritique(value:int):void
        {
            _chanceCritique = value;
        }
        
        public function get degats():int
        {
            return _degats;
        }
        
        public function set degats(value:int):void
        {
            _degats = value;
        }
        
        public function frapper(cible:Personnage):void
        {
            var degatsAppliques:int = degats;
            
            // On jette un dé à 100 faces : si le résultat est inférieur ou égal à la chance de coup critique, l'attaque fait un coup critique !
            if (Math.random() * 100 <= chanceCritique)
            {
                trace("Critique !");
                // On double les dégâts !
                degatsAppliques *= 2;
            }
            
            // On applique les dégâts
            cible.sante -= degatsAppliques;
        }
    }
}
Le programme

Le programme principal ne change pas par rapport à la solution précédente :

var epeeLegendaire:Arme = new Arme();
epeeLegendaire.degats = 80;
epeeLegendaire.chanceCritique = 50;

var hacheDeGuerre:Arme = new Arme();
hacheDeGuerre.degats = 40;
hacheDeGuerre.chanceCritique = 10;

var moi:Personnage = new Personnage();
moi.armeEquipee = epeeLegendaire;

var mechant:Personnage = new Personnage();
mechant.armeEquipee = hacheDeGuerre;

trace("Le méchant m'attaque ! ");
mechant.attaquer(moi);
trace("Il va connaître ma fureur ! A l'attaque !");
moi.attaquer(mechant);
Résultat

Enfin, voici le résultat de l'exécution de notre programme :

Le méchant m'attaque ! 
Il reste 60 PV à la cible.
Il va connaître ma fureur ! A l'attaque !
Il reste 20 PV à la cible.
En résumé
  • Il est possible d'utiliser des éléments dits statiques qui sont directement liés à la classe et non à ses instances.

  • Ces éléments statiques sont déclarés à l'aide du mot-clé static et facilitent l'ajout de fonctionnalités au programme.

  • Il est impossible d'utiliser le mot-clé this dans les méthodes statiques.

  • Nous pouvons créer des constantes qui sont similaires aux variables, mais qui ne peuvent pas être modifiées.

  • Pour déclarer une constante, nous devons utiliser le mot-clé const.

  • Il est possible de combiner les classes entre elles : les objets peuvent alors contenir d'autres objets.

Exemple de certificat de réussite
Exemple de certificat de réussite