Le monkey-patch, c’est le fait de pouvoir écraser des attributs, méthodes, etc… au moment de l’exécution. On s’en sert en général à remplacer ou étendre une méthode.
En JavaScript, le monkey-patching est extrêmement simple, puisqu’on peut nativement remplacer n’importe quelle méthode d’un objet par une fonction dynamiquement.
Remplacer une méthode
C’est trivial :
object.method = function (…) { // this === object // … }; |
Modifier le comportement d’une méthode
Supposons que j’ai la classe « Entity » possédant une méthode « save()« . On a un problème, cette méthode a tendance à enregistrer des données complètement corrompues mais on n’arrive pas à savoir dans quelles conditions ça arrive. Le plus simple serait de pouvoir se « brancher » sur cette méthode pour logger certaines informations afin d’en savoir plus.
Le monkey-patching est une façon simple d’y arriver :
// wrap to avoid global context pollution (function () { // store original method var _save = Entity.prototype.save; // override method Entity.prototype.save = function () { log_stack_trace(new Error().stack); // grab a stack trace to know where I've been called from log_entity(this); // log entity to check data // call original method _save.apply(this, arguments); }; })(); |
Quelques petits choses utiles à savoir quand on monkey-patch :
Function#applypour appeler la méthode d’origine.argumentsreprésente la liste des arguments passés à notre fonction, pas besoin de jongler avec le prototype de la fonction d’origine pour avoir les données qu’il faut.- Penser à imbriquer le tout dans une fonction anonyme pour éviter de dépendre du contexte global. Dans notre exemple si on ne l’avait pas fait, il aurait suffit que «
_save» soit surchargée ailleurs pour tout casser
- On peut affecter
Classe.prototype.methodepour impacter toutes les instances d’un coup (même celles déjà instanciées), ouobjet.methodepour n’impacter qu’un objet spécifique.
Note sur arguments
Un petit détail peu connu, que j’ai d’ailleurs découvert récemment sur Twitter (mais je n’arrive pas à remettre la main dessus) : quand on modifie un argument de la méthode, ça impacte aussi arguments.
Exemple :
trick(42); function trick (x) { console.log(x); // 42 console.log(arguments); // {'0': 42} x = 33; console.log(arguments); // {'0': 33} } |
C’est important à savoir, car quand on appelle methode_originale.apply(this, arguments) il faut être sûr de ce qu’on fait.
Quid de l’héritage de méthode
Lorsqu’on fait de l’héritage (même si l’héritage et JavaScript… enfin bon ce n’est pas le sujet) et qu’on souhaite surcharger une méthode, on va justement typiquement faire du monkey-patching.
On aurait pu traiter notre exemple précédent par héritage :
// Standard inheritance function LoggedEntity () { Entity.apply(this, arguments); } LoggedEntity.prototype = new Entity; // Override "save" LoggedEntity.prototype.save = function () { // log … // call parent method Entity.prototype.save.apply(this, arguments); } |
Le rapport avec l’AOP ?
Disclaimer : Je vais me faire assassiner par les puristes, donc je confirme d’avance que c’est une simplification des concepts pour illustrer le propos, pas un cours théorique, trèèèèès loin de là ! n’hésitez pas à corriger dans les commentaires si quelque chose vous semble vraiment absurde.
La Programmation Orientée Aspect consiste grosso-modo à étendre des méthodes plutôt que de fonctionner par héritage. Évidemment c’est un énorme raccourci, et l’AOP (POA en français donc) est accompagné de son vocabulaire specifique bien chiant, mais on peut à peu près résumer :
- Le « greffon » (« advice ») est le bout de code qu’on veut insérer dans la méthode originale
- Le « point de coupe » (« pointcut ») est la méthode qu’on surcharge
- Le « point de jonction » (« join point ») est l’endroit où on va injecter notre code :
À la place :
object.method = function () { // new code } |
Avant :
var _method = object.method; object.method = function () { // insert advice here // can modify "arguments" return _method.apply(this, arguments); } |
Après :
var _method = object.method; object.method = function () { var result = _method.apply(this, arguments); // insert advice here // can modify result return result; } |
Autour de :
var _method = object.method; object.method = function () { // insert first part of advice // can modify "arguments" var result = _method.apply(this, arguments); // insert last part of advice here // can modify result return result; } |
Conclusion
Dans une application Node, on peut ainsi étendre ou corriger un module tiers sans avoir à le copier-coller, et ainsi continuer à bénéficier des mises à jour. Je conseille d’isoler les patchs dans des fichiers à part dans l’application de manière à pouvoir les identifier simplement.
En bref, un concept simple et très utile (débugger un module sans éditer son code source est vraiment utile), mais à utiliser avec modération pour continuer à s’y retrouver

Comments:8
Laisser un commentaire