Monkey Patching
Or sometimes known as Duck Punching.
So what we’ve got here is an arbitrary object kimsObject
(line 01) thrown in the BINARYMIST
global, with a public method kimsMethod
(line 03).
The trace
method is passed an object and a method name (line 10). It replaces the specified
method with a new method that “wraps” additional functionality around the original method.
The call is made from line 23
var BINARYMIST = {};
BINARYMIST.kimsObject = (function () { return { kimsMethod: function () { alert("Now inside kimsMethod."); } }; }()); BINARYMIST.trace = (function () { return function (targetObject, targetMethod) { var originalMethod = targetObject[targetMethod]; var that = this; (function () { console.log(new Date(), "Entering:", targetMethod); var result = originalMethod.apply(that, arguments); console.log(new Date(), "Exiting:", targetMethod); return result; }(/*execute me!*/)); }; }()); (function () { BINARYMIST.trace(BINARYMIST.kimsObject, 'kimsMethod') }());
I believe if used with care, and in moderation.
Monkey Patching can support and provide implementation for
Dr. Barbara Liskov’s Liskov Substitution Principle (LSP)
Consider the developer that uses a function he/she thinks does something, but it actually does something entirely unexpected.
Be sure to make known to the team and those what will use your code what you have done, and what they can expect.
Make sure this is documented in code around the API, whether it be with comments or simply in the way you write your code.
Jeff Atwood has a good post on the dangers of using Monkey Patching.
Be sure to check it out.
You may or may not need the Object.create
function. It’s part of the ECMA Standard 262
which is the vendor-neutral standard for what was originally Netscape’s JavaScript.
This allows us to specify the object we want to be the prototype for our new object, I.E. our base class.
Properties added to an object‘s prototype are shared, through inheritance, by all objects sharing the prototype.
Alternatively, a new object may be created with an explicitly specified
prototype by using the Object.create
built-in function.
This also makes me think of what we try to achieve with Aspect Orientation in .NET.
Below I’ve had an attempt at trying to make the BINARYMIST.createAspectedObjectWithTracing
as generic as possible.
So that on the last line you can instantiate a BINARYMIST.kimsObject
and call the kimsMethod
method.
The way I’ve setup the BINARYMIST.createAspectedObjectWithTracing
, you should be able to instantiate any object and call which ever method on it that you desire.
You get the tracing functionality for free.
Using this sort of approach, you should be able to apply any functionality you want to any method you want.
I’m keen on hearing how this could be improved.
Making the call even more transparent.
BINARYMIST.kimsObject = (function () { return { kimsMethod: function () { alert("Now inside kimsMethod."); } }; }()); BINARYMIST.trace = (function () { return function (targetObject, targetMethod) { var originalMethod = targetObject[targetMethod]; var that = this; (function () { console.log(new Date(), "Entering:", targetMethod); // We can pass any number of args to the original method we wanted to call. // We also handle any return value. So hopefully this should be able to be used for any method. var result = originalMethod.apply(that, arguments); console.log(new Date(), "Exiting:", targetMethod); return result; }(/*execute me!*/)); }; }()); // Add create method to the Object function // Gives us dynamic control of what a base class should look like. // method (create) that takes an object argument, returns a new object that has the parameter assigned to it's prototype. // Creates a function on Object (create) that takes an object argument. // Creates a function (Func) and assigns the object parameter to the function definition's prototype. // Instantiates and assigns the new function (Func) to the "create" function definition on Object. (function () { if (typeof Object.create !== 'function') { Object.create = function (o) { var Func = function () {}; Func.prototype = o; return new Func(); }; } }()); BINARYMIST.createAspectedObjectWithTracing = (function (anyObject, anyFunction) { var localObject = Object.create(anyObject); localObject[anyFunction] = function () { return BINARYMIST.trace(anyObject, anyFunction); }; return localObject; }); // Instantiate your object (any object) and call a method on it. BINARYMIST.createAspectedObjectWithTracing(BINARYMIST.kimsObject, 'kimsMethod').kimsMethod();
Now as you can see again, all of the objects I’ve defined are sitting tidily out of the global object.
Now as you can see below, our BINARYMIST.kimsObject
is now part of the new object’s (that localObject
references) prototype object.
and it’s method is clearly visible.
On line 42 above, we set the localObject
‘s kimsMethod
to reference a wrapped version of BINARYMIST.kimsObject.kimsMethod
.
The wrapped version has the tracing added.
We then return the localObject
which references BINARYMIST.kimsObject
and call the most specific kimsMethod
which has the functionality we just set.
return BINARYMIST.trace(anyObject, anyFunction);
and as you can see below, we’re now ready to start playing with our target method (kimsMethod).
We then assign the this.
We then execute the anonymous closure.
In doing so, this
is bound to the global object.
So to access the outer scope we need the that
.
We then apply the originalMethod
to that
(BINARYMIST
in our case).
Tags: aspect orientation, Curry, JavaScript, Monkey Patching
September 29, 2012 at 16:48 |
JavaScript Weekly also sent this link out:
http://javascriptweekly.us1.list-manage1.com/track/click?u=0618f6a79d6bb9675f313ceb2&id=9681652715&e=3531157141
July 6, 2013 at 13:05 |
[…] https://blog.binarymist.net/2012/05/27/extending-currying-and-monkey-patching-part-3/ […]