Vous vous souvenez que dans la première partie du cours, vous avez découvert la notion de prototype ? Je vous avais dit que le moteur de la création d’objets était centré sur cette notion. Mon petit doigt me dit que vous allez aborder ce sujet dans ce chapitre. ;)
Permettez à vos utilisateurs de lancer une vidéo avec l’aide d’un Decorator
Dans ce chapitre, vous allez voir comment implémenter un Decorator pour ajouter une fonctionnalité à un objet. Comme pour les précédents chapitres, j’ai créé une issue sur GitHub.
C’est bon, vous l’avez vue ? Alors, on peut continuer ! :)
Si je résume la situation avec cette nouvelle issue :
Certains films, ceux provenant du fichier
data/external-movie-data.json
, contiennent maintenant un champtrailer_id
. Ce champ correspond à l’ID YouTube de la vidéo. Au clic, une fenêtre modale s’ouvre, et on peut voir le teaser du film.Les autres films, ceux provenant du fichier
data/new-movie-data.json
, n’ont pas cette fonctionnalité.
J’ai créé un nouveau fichier que j’ai appelé templates/PlayerModal.js
. Ce fichier contient notamment le code ci-dessous.
const player = `
<div class="player">
<iframe
height="600"
width="800"
src=${this.movie.trailer}
></iframe>
<button class="close-btn">Fermer la fenêtre<button>
</div>
`
C’est ce morceau de code qui va me permettre d’afficher mon iFrame comme dans la capture d’écran ci-dessous.
Ici, le challenge va être de vouloir ajouter une nouvelle fonctionnalité à l’objet templates/MovieCard.js
en le modifiant le moins possible.
createMovieCard() {
const movieCard = `
<div class="movie-thumbnail center">
<img
alt="${this._movie.title}"
src="${this._movie.thumbnail}"
/>
</div>
<h3 class="fs-16 center">${this._movie.title}</h3>
<p class="fs-14 center">
<span>${this._movie.released_in}</span>
-
<span>${this._movie.duration}</span>
</p>
`
this.$wrapper.innerHTML = movieCard
return this.$wrapper
}
Vous pourriez tout à fait complexifier le code ci-dessus en lui ajoutant un traitement complémentaire, mais vous prendriez le risque de faire grossir encore et encore cet objet. Vous allez vous servir d’un Decorator pour implémenter cette solution. ;)
Identifiez les caractéristiques du Decorator Pattern
Un Decorator Pattern est composé de trois acteurs :
Le premier est le
Client
. C’est, ici, l’objet ou la fonction qui va appeler le Decorator. Dans le cas du projet, le client va être l’objetApp
.Le deuxième est le
Component
. C’est l’objet sans la nouvelle fonctionnalité. Pour Filmo Patterns, le component estMovieCard
. Ici, cet objet ne sait pas qu’il est “décoré” (on pourrait aussi dire qu’il est surchargé).Le troisième acteur est le
Decorator
. Cet objet va récupérer un objet existant, le surcharger (en lui ajoutant une nouvelle fonctionnalité), et va le retourner.
Ce design pattern est très répandu dans le monde de la programmation : il permet de rapidement ajouter de nouvelles fonctionnalités à un objet sans avoir à le surcharger ou à le complexifier. Bon, bien sûr, il est important de garder une certaine rigueur lors de l’implémentation d’un Decorator : n’essayez surtout pas de surcharger un Decorator qui surcharge un Decorator et ainsi de suite. ;)
Alors, comment se présente un Decorator ?
Jetez un coup d'œil au code ci-dessus : ;)
class Movie {
constructor(title, duration) {
this._title = title
this._duration = duration
}
createMovieTitle() {
return `
<h1 class="movie-title">${this._title}</h1>
`
}
}
function movieWithAgeLimit(movie, ageLimit) {
// Grâce au prototypage, je peux facilement ajouter de nouvelles propriétés et méthodes sur un objet existant
movie.ageLimit = ageLimit
movie.canSeeMovie = () => {
console.log(`Vous devez avoir plus de ${movie.ageLimit} pour voir ${movie._title}`)
}
// Ici le return est important, c'est ce qui me permet de retourner une version modifiée de l'objet Movie
return movie
}
// J'instancie un objet Movie "simple"
const MovieWithoutAgeLimit = new Movie('Batman Returns', 130)
// J'instancie un objet Movie décoré
const MovieWithAgeLimit = movieWithAgeLimit(new Movie('Alien', 120), 16)
Dans le code ci-dessus, la fonction movieWithAgeLimit
me permet rapidement et simplement d’ajouter de nouvelles fonctionnalités à un objet existant. C’est rendu possible grâce au prototypage !
Implémentez un Decorator Pattern
Pour implémenter un Decorator, je vais le plus souvent créer un dossier decorators
, ou je peux même le mettre dans le même fichier que mon objet existant. Le nommage du Decorator
est important : essayez de reprendre celui de l’objet surchargé en précisant la fonctionnalité que vous ajoutez. Dans l’exemple ci-dessus, pour créer une limite d’âge, j’ai nommé ma fonction movieWithAgeLimit
.
Dans la vidéo ci-dessous, je vais vous montrer comment intégrer un Decorator Pattern sur un objet User
.
Vous pouvez retrouver le code de cette implémentation sur ce Gist GitHub.
Comme vous pouvez le constater, un Decorator est un design pattern simple et pratique à mettre en place. Je pense que c’est l’un des premiers que vous pouvez mettre en pratique dans vos projets.
À vous de jouer !
Dans cette section, je vais vous demander de résoudre notre issue GitHub. Vous allez donc devoir implémenter un Decorator pour l’objet templates/MovieCard.js
. J’ai déjà créé :
Le fichier
templates/PlayerModal.js
. C’est ce fichier qui va vous permettre d’afficher l’iframe YouTube.Le fichier
decorators/movieCardWithPlayer.js
. C’est ce fichier qui va contenir le decorator ; j’ai aussi déjà créé la fonction et je l’ai ajoutée dans le fichierindex.html
.
Voilà, il ne vous reste plus qu’à mettre en pratique. Une fois que vous avez suffisamment cherché et que vous avez trouvé la solution (ou au contraire que vous butez un peu), vous pouvez regarder la vidéo ci-dessous !
Le code source contenant la solution de cet exercice se trouve sur la branche partie-3/chapitre-2-fin
.
En résumé
Un Decorator permet d’ajouter une fonctionnalité à un objet existant sans avoir à la complexifier. Il prend en paramètre l’objet en question, le modifie et le retourne.
En JavaScript, la création de Decorator est fortement simplifiée grâce au prototypage. C’est ce mécanisme qui nous permet d’ajouter des propriétés et méthodes à un objet.
Il est possible que vous voyiez parfois des Decorators implémentés avec un
@
. Cela dit, ce mécanisme reste expérimental aujourd’hui en JavaScript.
Et de deux !
Vous venez d’apprendre votre deuxième Structural Design Pattern et vous commencez maintenant à avoir une batterie d’outils à votre disposition pour maintenir votre codebase. Dans le prochain chapitre, nous allons voir le Proxy Pattern, qui va nous permettre de faciliter la mise en cache. :)