Monkeypatch

Changes an object without affecting the original code.



Usage in JavaScript:
low


Fundamentals


The Monkeypatch pattern allows you to modify code at runtime without changing the original source code. The use of this pattern is somewhat controversial, but it can be very helpful in situations where you need to correct errors in 3rd party controls or libraries until a fix becomes available (and at that point you can remove your Monkeypatch). Monkeypatch is also referred to as Duck Punching.

How is monkey patching implemented? Actually, it is fairly simple: it works by replacing existing properties or methods on an object. Let's look at an example. We have a person object that has a say method which displays its name and age. The developer wants to make the person appear a few years younger and decides to monkey patch the say method:

var person = { 
    name: "Kevin",
    age: 39,
    say: function () {
        alert(this.name + ", " + this.age + " years old");
    }
};

person.say();   // => Kevin, 39 years old

// monkey patching say

person.say = function () {
    var younger = this.age - 5;
    alert(this.name + ", " + younger + " years old");
}; 

person.say();   // => Kevin, 34 years old
Run

Because of the dynamic nature of JavaScript there is nothing that prevents developers from overwriting or modifying public members on an existing object.

In the above example we wholesale replaced the original say with a new definition. What is more common though is that the original method needs a slight adjustment possibly just before and after the method execution without changing the original functionality. Here is one way to handle this situation:

var person = { 
    name: "Kevin",
    age: 39,
    say: function () {
        alert(this.name + ", " + this.age + " years old");
    }
};

person.say();   // => Kevin, 39 years old

// monkey patching say 

var oldSay = person.say;

person.say = function () {
    this.age -= 5;
    oldSay.call(this);
    this.age += 5;
};

person.say();   // => Kevin, 34 years old
Run

First, the original say method is saved off into a variable. Next, the person.say method is redefined in which the age is adjusted just before calling the original method and then restored to its original value.

The above code adds a variable to the global namespace which we usually try to avoid. We can correct this by wrapping the monkey patch in an immediate function. Its closure will hold a reference to the original method which is accessible to the redefined person.say method. Here is what this looks like:

var person = { 
    name: "Kevin",
    age: 39,
    say: function () {
        alert(this.name + ", " + this.age + " years old");
    }
};

person.say();   // => Kevin, 39 years old

// monkey patching say with immediate function

(function () {

     var oldSay = person.say;

     person.say = function () {
         this.age -= 5;
         oldSay.call(this);
         this.age += 5;
     };
})();

person.say();   // => Kevin, 34 years old
Run

That looks good. The function is redefined within an immediate function and its closure maintains a local variable called oldSay which gets assigned to the original say method. We were able to inject pre-processing and post-processing functionality surrounding the original say method call.

Did you notice that the source code of the person object (in this case the object literal at the top of the code snippet) was not touched at all? And, yet, we were able to alter its functionality. This is the Monkeypatch pattern in all its glory. It's a powerful pattern that allows developers to make runtime changes to any object, whether you own it or not.

What if, in the above example, the method was defined on the object's prototype rather than on the object itself? In that case we are not really overwriting the original but masking it instead. Remember that the JavaScript engine searches the prototype chain starting at the object itself until if finds the requested member.

However, you can also monkey patch the prototype object so that the change applies to all instances that share the same prototype object. Here is what this looks like:

var Person = function (name, age) { 
    this.name = name;
    this.age = age; 
};

Person.prototype.say = function () {
    alert(this.name + ", " + this.age + " years old");
}

var kevin = new Person("Kevin", 39);
var sonya = new Person("Sonya", 50);

kevin.say();   // => Kevin, 39 years old
sonya.say();   // => Sonya, 50 years old

// monkey patching prototype object 

(function () {

     var oldSay = Person.prototype.say;

     Person.prototype.say = function () {
         this.age -= 5;
         oldSay.call(this);
         this.age += 5;
     };
})();

kevin.say();   // => Kevin, 34 years old
sonya.say();   // => Sonya, 45 years old

Run

Instead of an object literal we now have a Person constructor function. We create two persons. Their say method is defined on the prototype object. By monkey patching the method on the prototype we affect all person instances. Both Kevin and Sonya appear 5 years younger than they are.

Again, this is a powerful pattern but it is not without its perils. If you are not clear about the intricacies of the original code you may unintentionally introduce bugs that affect other parts of the application. It gets worse when multiple patches are applied as they have a tendency to interact in unpredictable, combinatorial ways.

It has been said that with the C language you can shoot yourself in the foot. With C++ you can shoot your leg off. With JavaScript and monkey patching you can blow yourself up and the whole team with it. Clearly with all this power comes great responsibility.

In reality monkey patching is not a widely used practice in JavaScript (at least for now). Remember, monkey patching can be applied to built-in objects as well, so you have the ability to redefine the language's core features turning it into a different language altogether with a its own syntax. In other languages, such as Ruby and Python, dynamic extensions of core classes (i.e. monkey patching) is more commonly used. There are developers in those communities that are pushing back hard, stating that monkey patching is 'destroying the language'.

Some language purists will tell you that monkey patching is a hack that should never be allowed. However, it can be a valuable tool to temporarily correct bugs in 3rd party components or libraries over which you have no direct control. It is better to use it sparingly and when you do, be very careful, document it well, and share this information with your team. Then, as soon as a fix becomes available you immediately remove it. Fortunately, removing monkey patches is very easy.



  Mixin
Lazy Load