Overview
Monkey Patching is technique to "extend or modify the runtime code of dynamic languages without altering the original source code". Wikipedia then goes on to outline four common used for monkey patching:
- Replace methods/attributes/functions at runtime. (e.g. stub out a function during testing)
- Modify/extend behavior of a third-party product without maintaining a private copy of the source code.
- Apply a patch at runtime to the objects in memory, instead of the source code on disk.
- Distribute security or behavioral fixes that live alongside the original source code. (e.g. distributing the fix as a plugin)
Patching a Third-Party Library
After working for years on a very large web platform, I have quickly learned one important lesson: never directly modify the source of a third-party library! Doing so makes upgrading the library a total nightmare! Read this blog post for a more detailed explanation. So what do you do if you're waiting for the library creators to either A) add your needed feature or B) fix a bug? Why you monkey patch of course!
In the following example, I'm monkey patching jQuery's .appendTo()
function to alternate a pages background color based on the amount of children elements on document.body
.
// create a closure and remap jQuery to $
(function($){
// save off original method
var _originalAppendTo = $.fn.appendTo;
// override method
$.fn.appendTo = function() {
// silly code to alternate backgound color
if ($(document.body).children().length % 2) {
document.body.style.background = "#f285cf";
} else {
document.body.style.background = "#85C3F2";
}
// call original passing in scope and arguments
return _originalAppendTo.apply(this, arguments);
};
})(jQuery);
Dissecting the above code, first we create a closure around the patch to remap jQuery to $. This also gives us the ability to use local variables if need be. Next, we store a reference to the original function before we mask it. This gives us the ability to call the original at any point in our masking function. After this, we then declare the masking function on top of the originals namespace ($.fn.appendTo
). Finally, we implement our patch and are able to call back to the original at anytime. Sweet!
Here is the full example implemented on JsFiddle:
Potential Problems
As with any programming construct, monkey patching has both benefits and pitfalls. I have just shown you a few examples of the benefits. Now here are some of the potential problems:
- They can lead to upgrade problems when the patch makes assumptions about the patched class/object/framework that are no longer true.
- If multiple modules attempt to monkey patch the same method, one of them (whichever one runs last) "wins" and the other patch has no effect. Wrap your patches in a closure to avoid this.
- Discrepancies between the original source code and the observed behavior can be very confusing to anyone unaware of the patch(es) existence.
With all this being said, all uses of the monkey patching pattern should be heavily scrutinized and implemented with caution as it can lead to confusion and bugs on larger projects. Now get out there and...