Mis à jour le 16/01/2018
  • 20 heures
  • Facile

Ce cours est visible gratuitement en ligne.

Ce cours est en vidéo.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

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

J'ai tout compris !

Trop classe, la POO !

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

Vous venez d'apprendre à créer vos premiers objets en JavaScript. Ce chapitre va vous permettre de mieux connaître leur fonctionnement et de découvrir certaines possibilités offertes par la programmation orientée objet.

Un mini-jeu de rôle, version multijoueurs

Dans le chapitre précédent, nous avons créé un mini-jeu de rôle dont voici pour mémoire le code source.

var perso = {
    nom: "Aurora",
    sante: 150,
    force: 25,
    xp: 0,

    // Renvoie la description du personnage
    decrire: function () {
        var description = this.nom + " a " + this.sante + " points de vie, " +
            this.force + " en force et " + this.xp + " points d'expérience";
        return description;
    }
};

console.log(perso.decrire());

// Aurora est blessée par une flèche
perso.sante = perso.sante - 20;

// Aurora trouve un bracelet de force
perso.force = perso.force + 10;

// Aurora apprend une nouvelle compétence
perso.xp = perso.xp + 15;

console.log(perso.decrire());

Pour l'enrichir, on souhaite que plusieurs personnages puissent participer. Voici donc Glacius, le compagnon d'Aurora.

var perso2 = {
    nom: "Glacius",
    sante: 130,
    force: 30,
    xp: 0,

    // Renvoie la description du personnage
    decrire: function () {
        var description = this.nom + " a " + this.sante + " points de vie, " +
            this.force + " en force et " + this.xp + " points d'expérience";
        return description;
    }
};

On remarque immédiatement que nos deux objetsperso etperso2 se ressemblent beaucoup. Aurora et Glacius ont exactement les mêmes propriétés (nom, santé, force, capacité à se décrire) mais avec des valeurs différentes.

Vous savez maintenant que la duplication de code est votre ennemie jurée. Il faudrait pouvoir centraliser dans un modèle ce qui caractérise un personnage, puis créer chaque personnage sur la base de ce modèle. Comment faire ?

Objets et prototypes en JavaScript

Pour créer des modèles d'objet en JavaScript, on utilise les prototypes. En plus de ses propriétés particulières, tout objet JavaScript possède une propriété interne appelée prototype. Il s'agit d'un lien (on parle de référence) vers un autre objet. Lorsqu'on essaie d'accéder à une propriété qui n'existe pas dans un objet, JavaScript essaie de trouver cette propriété dans le prototype de cet objet.

Voici un exemple illustrant ce fonctionnement.

var unObjet = {
    a: 2
};

// Crée unAutreObjet avec unObjet comme prototype
var unAutreObjet = Object.create(unObjet);

console.log(unAutreObjet.a); // Affiche 2

Tapez l'exemple ci-dessus dans le fichiercours.js du répertoirechapitre_8/js (à créer), et testez-le avec le fichiercours.html situé dans le répertoirechapitre_8/html (à créer également). Vous obtenez le résultat suivant.

Dans cet exemple, l'instruction JavaScriptObject.create() est utilisée pour créer l'objetunAutreObjet en lui donnant comme prototype l'objetunObjet. Lors de l'appel àunAutreObjet.a, c'est la propriétéa deunObjet qui est utilisée puisque la propriétéan'existe pas dansunAutreObjet.

Si le prototype d'un objet ne possède pas une propriété recherchée, alors c'est dans son propre prototype que la recherche continue, jusqu'à arriver à la fin de chaîne des prototypes. Si la propriété n'a été trouvée dans aucun objet, son accès renvoie la valeurundefined.

var unObjet = {
    a: 2
};

// Crée unAutreObjet avec unObjet comme prototype
var unAutreObjet = Object.create(unObjet);

console.log(unAutreObjet.a); // Affiche 2

// Crée encoreUnObjet avec unAutreObjet comme prototype
var encoreUnObjet = Object.create(unAutreObjet);

console.log(encoreUnObjet.a); // Affiche 2
console.log(encoreUnObjet.b); // Affiche undefined

Ce mode de relation entre les objets JavaScript est appelé délégation : un objet délègue une partie de son fonctionnement à son prototype.

Un prototype pour nos personnages

Création des personnages

Essayons d'appliquer notre compréhension nouvelle du fonctionnement des objets en JavaScript pour améliorer le code de notre jeu de rôle d'exemple. Tapez le code ci-dessous dans un fichier nomméjdr.js situé dans le répertoirechapitre_8/js, et testez-le avec un fichierchapitre_8/html/jdr.html.

var Personnage = {
    nom: "",
    sante: 0,
    force: 0,
    xp: 0,

    // Renvoie la description du personnage
    decrire: function () {
        var description = this.nom + " a " + this.sante + " points de vie, " +
            this.force + " en force et " + this.xp + " points d'expérience";
        return description;
    }
};

var perso1 = Object.create(Personnage);
perso1.nom = "Aurora";
perso1.sante = 150;
perso1.force = 25;

var perso2 = Object.create(Personnage);
perso2.nom = "Glacius";
perso2.sante = 130;
perso2.force = 35;

console.log(perso1.decrire());
console.log(perso2.decrire());

Le résultat de son exécution est le suivant.

Dans cet exemple, nous avons créé un objet nomméPersonnage, qui rassemble les propriétés communes à tous les personnages. Les objetsperso1 etperso2 sont créés avec l'objetPersonnage comme prototype, et lui délèguent les fonctionnalités communes.

Initialisation des personnages

On peut noter que le processus de création d'un personnage est un peu répétitif : pour chaque personnage, il faut successivement donner une valeur à chacune de ses propriétés. On peut faire mieux en créant une fonction d'initialisation dans l'objetPersonnage.

var Personnage = {
    // Initialise le personnage
    init: function (nom, sante, force) {
        this.nom = nom;
        this.sante = sante;
        this.force = force;
        this.xp = 0;
    },

    // Renvoie la description du personnage
    decrire: function () {
        var description = this.nom + " a " + this.sante + " points de vie, " +
            this.force + " en force et " + this.xp + " points d'expérience";
        return description;
    }
};

var perso1 = Object.create(Personnage);
perso1.init("Aurora", 150, 25);

var perso2 = Object.create(Personnage);
perso2.init("Glacius", 130, 30);

console.log(perso1.decrire());
console.log(perso2.decrire());

La méthode init() prend en paramètres les valeurs initiales des propriétés d'un personnage, et définit les propriétés associées. A l'intérieur de cette méthode, il faut bien distinguer les propriétés (préfixées par le mot-cléthis) des paramètres (non préfixés). Par exemple,this.nom représente la propriéténom de l'objet etnom correspond à l'un des paramètres de la méthode.

Le code de création d'un personnage ne comporte plus que deux étapes :

  • La création proprement dite, avec l'objetPersonnage comme prototype.

  • L'initialisation des propriétés, à l'aide de la fonctioninit() de l'objetPersonnage.

Des adversaires pour nos héros

Malgré l'ajout d'un second personnage, notre mini-jeu reste bien limité. Que lui manque-t-il ? Des ennemis à combattre, bien sûr !

Tout comme un joueur,  un adversaire simulé par l'ordinateur aura un nom, des points de vie et de force. En revanche, un ennemi ne gagnera pas de points d'expérience, mais possèdera deux caractéristiques particulières : sa race et le nombre de points d'expérience gagnés lorsqu'il sera tué par un joueur.

Joueurs et adversaires sont donc tous deux des personnages, avec des points communs et des spécificités qui les distinguent. Notre nouvelle modélisation objet reflète cette distinction.

var Personnage = {
    // Initialise le personnage
    initPerso: function (nom, sante, force) {
        this.nom = nom;
        this.sante = sante;
        this.force = force;
    }
};

var Joueur = Object.create(Personnage);
// Initialise le joueur
Joueur.initJoueur = function (nom, sante, force) {
    this.initPerso(nom, sante, force);
    this.xp = 0; // L'expérience du joueur est toujours initialisée à 0
};
// Renvoie la description du joueur
Joueur.decrire = function () {
    var description = this.nom + " a " + this.sante + " points de vie, " +
        this.force + " en force et " + this.xp + " points d'expérience";
    return description;
};

var Adversaire = Object.create(Personnage);
// Initialise l'adversaire
Adversaire.initAdversaire = function (nom, sante, force, race, valeur) {
    this.initPerso(nom, sante, force);
    this.race = race;
    this.valeur = valeur;
};

// ...

Nous créons d'abord un objetPersonnage qui est le modèle commun à tous les personnages. Il possède les propriétés communes à tous les personnages (nom, santé, force) ainsi qu'une méthode pour les initialiser.

Les objetsJoueur etAdversaire sont tous deux créés avecPersonnage comme prototype. Ils disposent chacun d'une fonction d'initialisation particulière, qui fait appel par délégation à la méthode initPerso()  de l'objetPersonnage. Enfin, l'objetJoueur possède une fonction de description.

Une fois ces objets modèles définis, nous pouvons les utiliser pour créer nos personnages : les joueurs Aurora et Glacius (avecJoueur comme prototype), ainsi que le vilain monstre ZogZog (avecAdversaire comme prototype).

// ...

var joueur1 = Object.create(Joueur);
joueur1.initJoueur("Aurora", 150, 25);

var joueur2 = Object.create(Joueur);
joueur2.initJoueur("Glacius", 130, 30);

console.log("Bienvenue dans ce jeu d'aventure ! Voici nos courageux héros :");
console.log(joueur1.decrire());
console.log(joueur2.decrire());

var monstre = Object.create(Adversaire);
monstre.initAdversaire("ZogZog", 40, 20, "orc", 10);

console.log("Un affreux monstre arrive : c'est un " + monstre.race + " nommé " + monstre.nom);

Le mécanisme des prototypes permet aux objetsjoueur1,joueur2 etmonstre de bénéficier des propriétés définies dans les objetsJoueur etAdversaire, qui eux-mêmes profitent de celles de l'objetPersonnage.

Remplacez le code actuel du programmejdr.js par les deux exemples ci-dessus, puis testez ce programme. Vous obtenez le résultat suivant.

3, 2, 1... Fight !

Que serait un jeu de rôle sans des combats acharnés entre héros et monstres ? Ajoutons à notre mini-jeu la possibilité pour les joueurs et les adversaires de s'affronter.

Voici comment nous allons introduire les combats dans notre jeu. Un joueur peut attaquer un adversaire, mais l'inverse est aussi vrai. Un personnage attaqué voit ses points de vie diminués de la valeur de force de l'attaquant. Si ce nombre de points de vie tombe à zéro, alors le personnage meurt. Si son vainqueur est un joueur, il reçoit un nombre de points d'expérience égal à la valeur de l'adversaire tué.

On peut donc considérer que l'attaque est une capacité commune aux joueurs et aux adversaires, avec une particularité (le gain d'expérience en cas de victoire) spécifique aux joueurs. Voici la modélisation objet associée.

var Personnage = {
    // Initialise le personnage
    initPerso: function (nom, sante, force) {
        this.nom = nom;
        this.sante = sante;
        this.force = force;
    },
    // Attaque un personnage cible
    attaquer: function (cible) {
        if (this.sante > 0) {
            var degats = this.force;
            console.log(this.nom + " attaque " + cible.nom + " et lui fait " + degats + " points de dégâts");
            cible.sante = cible.sante - degats;
            if (cible.sante > 0) {
                console.log(cible.nom + " a encore " + cible.sante + " points de vie");
            } else {
                cible.sante = 0;
                console.log(cible.nom + " est mort !");
            }
        } else {
            console.log(this.nom + " ne peut pas attaquer : il est mort...");
        }
    }
};

var Joueur = Object.create(Personnage);
// Initialise le joueur
Joueur.initJoueur = function (nom, sante, force) {
    this.initPerso(nom, sante, force);
    this.xp = 0;
};
// Renvoie la description du joueur
Joueur.decrire = function () {
    var description = this.nom + " a " + this.sante + " points de vie, " +
        this.force + " en force et " + this.xp + " points d'expérience";
    return description;
};
// Combat un adversaire
Joueur.combattre = function (adversaire) {
    this.attaquer(adversaire);
    if (adversaire.sante === 0) {
        console.log(this.nom + " a tué " + adversaire.nom + " et gagne " +
            adversaire.valeur + " points d'expérience");
        this.xp += adversaire.valeur;
    }
};

var Adversaire = Object.create(Personnage);
// Initialise l'adversaire
Adversaire.initAdversaire = function (nom, sante, force, race, valeur) {
    this.initPerso(nom, sante, force);
    this.race = race;
    this.valeur = valeur;
};

// ...

L'objetPersonnage possède une nouvelle méthodeattaquer() qui gère l'attaque d'une cible ainsi que les cas particuliers associés (mort de la cible ou attaquant déjà mort). L'objetJoueur gagne une méthodecombattre() qui fait appel par délégation à la méthodeattaquer() dePersonnage et gère le gain d'expérience si l'adversaire meurt durant l'attaque. L'objetAdversaire n'est pas modifié, mais peut malgré tout attaquer un jouer grâce à la méthodeattaquer() dePersonnage, dont il bénéficie par délégation.

Il ne nous reste plus qu'à utiliser ces objets pour mettre en scène un combat sans merci entre les joueurs et le monstre.

// ...

var joueur1 = Object.create(Joueur);
joueur1.initJoueur("Aurora", 150, 25);

var joueur2 = Object.create(Joueur);
joueur2.initJoueur("Glacius", 130, 30);

console.log("Bienvenue dans ce jeu d'aventure ! Voici nos courageux héros :");
console.log(joueur1.decrire());
console.log(joueur2.decrire());

var monstre = Object.create(Adversaire);
monstre.initAdversaire("ZogZog", 40, 20, "orc", 10);

console.log("Un affreux monstre arrive : c'est un " + monstre.race + " nommé " + monstre.nom);

monstre.attaquer(joueur1);
monstre.attaquer(joueur2);

joueur1.combattre(monstre);
joueur2.combattre(monstre);

console.log(joueur1.decrire());
console.log(joueur2.decrire());

ZogZog attaque nos deux héros, qui répliquent ensuite. L'exécution de ce programme final donne le résultat suivant.

Testez avec d'autres valeurs initiales pour les propriétéssante etforce des personnages : vous obtenez un résultat final différent.

Conclusion

Vous connaissez à présent les grands principes de la programmation orientée objet, qui consiste à écrire des programmes en utilisant des objets. La POO permet de rassembler des données et des comportements (les méthodes) dans des entités appelées des objets.

Le modèle objet de JavaScript se base sur des prototypes pour créer des modèles et partager des propriétés entre plusieurs objets. Chaque objet a un prototype et une propriété absente d'un objet sera recherchée dans la chaîne de ses prototypes.

Même si elle reste très employée à l'heure actuelle, la POO n'est pas l'unique moyen de créer des programmes efficaces. Il est tout à fait possible de combiner l'utilisation d'objets et de simples fonctions au sein d'un même programme, voire de ne pas utiliser du tout d'objets !

A vous de jouer !

Vérifions vos nouveaux acquis avec quelques exercices autour de la POO. Ecrivez-les dans le répertoirechapitre_8

Modélisation de plusieurs chiens (résultat à obtenir)

Complétez le programmechiens.js ci-dessous pour ajouter la définition de l'objetChien et obtenir le résultat ci-après.

// TODO : ajoutez ici la définition de l'objet Chien
    
var crokdur = Object.create(Chien);
crokdur.init("Crokdur", "mâtin de Naples", 75);
console.log(crokdur.nom + " est un " + crokdur.race + " mesurant " + crokdur.taille + " cm");
console.log("Tiens, un chat ! " + crokdur.nom + " aboie : " + crokdur.aboyer());

var pupuce = Object.create(Chien);
pupuce.init("Pupuce", "bichon", 22);
console.log(pupuce.nom + " est un " + pupuce.race + " mesurant " + pupuce.taille + " cm");
console.log("Tiens, un chat ! " + pupuce.nom + " aboie : " + pupuce.aboyer());

Gestion de l'inventaire des personnages (résultat à obtenir)

Enrichissez le programmejdr.js issu du cours pour y ajouter la gestion de l'inventaire des personnages. Voici les améliorations à intégrer :

  • L'inventaire d'un personnage se compose d'un nombre de pièces d'or et d'un nombre de clés.

  • Tous les personnages possèdent initialement 10 pièces d'or et une clé.

  • L'inventaire doit être affiché dans la description d'un joueur.

  • Lorsqu'un joueur tue un adversaire, il récupère dans son propre inventaire le nombre de pièces d'or et de clés de cet adversaire.

Voici le résultat d'exécution à obtenir.

Comptes bancaire et comptes épargne (résultat à obtenir)

Complétez le programmecomptes.js ci-dessous pour ajouter la définition des objets nécessaires à son fonctionnement.

Par rapport à un compte bancaire, un compte épargne a la particularité de posséder un taux d'intérêt (exemple : 0,05 = 5%). Ce taux est utilisé pour calculer le montant des intérêts, qui est ensuite ajouté au solde du compte.

// TODO : ajoutez ici la définition des objets nécessaires

var compte1 = Object.create(CompteBancaire);
compte1.initCB("Alex", 100);
var compte2 = Object.create(CompteEpargne);
compte2.initCE("Marco", 50, 0.05);

console.log("Voici l'état initial des comptes :");
console.log(compte1.decrire());
console.log(compte2.decrire());

var montant = Number(prompt("Entrez le montant à transférer entre les comptes :"));
compte1.debiter(montant);
compte2.crediter(montant);

// Calcule et ajoute les intérêts au solde du compte
compte2.ajouterInterets();

console.log("Voici l'état final des comptes après transfert et calcul des intérêts :");
console.log(compte1.decrire());
console.log(compte2.decrire());

Voici le résultat d'exécution à obtenir avec un virement de 50 euros.

Solutions des exercices

Le code source des solutions est consultable en ligne

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