category

article

Taking off dynamically added behavior in Javascript

Posted on: August 28th, 2012

After reading some articles on the Decorator design pattern, I started searching for Javascript implementations that make it possible to remove the dynamically added behaviour. What if we want to use multiple decorators on an object, but want to keep the option to take some of them off later, while keeping others on?

I dug deeper on the Web, and found some discutions on this topic (example #1, example #2 etc), but no Javascript implementations. I decided to experiment with this on my own, on the Decorator and Chain of Responsibility design patterns.

First, the Decorator. Here is its most common form in Javascript (see Wikipedia example):

var target,
    DemoDecorator1, DemoDecorator2;

// the object to be decorated
target = {
    someNumber: 0,
    getNumberString: function(){
        return "" + this.someNumber;
    }
};

// decorators
DemoDecorator1 = function(objToDecorate){
    var newNumber = objToDecorate.getNumberString() + " 1";

    objToDecorate.getNumberString = function(){
        return newNumber;
    };

    return objToDecorate;
};

DemoDecorator2 = function(objToDecorate){
    var newNumber = objToDecorate.getNumberString() + " 2";

    objToDecorate.getNumberString = function(){
        return newNumber;
    };

    return objToDecorate;
};

// The client code, that uses these decorator functions:
console.log(target.getNumberString());  // => "0"

target = new DemoDecorator2(new DemoDecorator1(target));
console.log(target.getNumberString());  // => "0 1 2";

Please notice that the decorator functions are used in a new statement, but the decorator instance is not returned from the constructor. Instead, the initial target object is augmented and returned. To return something from a function that is to be used as a constructor is generally an anti-pattern in Javascript, but this is in fact the only kind of good usage that I know of.

Further, you can see that the target has been augmented, and you can imagine a more complicated scenario, where you have a bunch of decorators, and you want to take them off and put them back on. In that scenario, this solution would not work.

Before picking a path towards a solution, I’ve assumed that we might find ourselves in scenarios with swarms of objects that need to be decorated multiple times (perhaps in a Javascript game), so we don’t want to store lots clones of the intermediary states we get by decorating. Therefore, it seems cheaper to override the methods on the original objects, and just store the overridden methods.

Here’s my take on this - since the decorator is a constructor, and therefore creates an instance that this points to inside of it, we can store data on that instance - to be more precise, we will store the backed up methods. Again: there’s the decorator constructor, which creates the decorator instance, which is used to store the backed up methods.

I will stick to the functionality seen in the first code example to demo this - methods that concatenate strings to the string returned by the overridden method. So if the original demo method returns "0", the demo method that’s defined on the decorator will wrap it, and return "0 1" etc.

The newly decorated object must somehow access the properties available on the target object in it’s previous state. This means that it should have a utility method attached to the target during the decoration - let’s call it overriddenMethod. So if before the wrap the demo method returned "0", then on the decorator, we can define the new demo method:

demo: function(){
    var oldDemoFn = this.overriddenMethod('demo');
    return (oldDemoFn ? oldDemoFn() : '') + ' 1';
}

Here’s the way we retrieve the overridden methods:

Decorator.prototype = {
    ...
    overriddenMethod: function(methodName){
        return this.methodsBackup[methodName];
    },
    ...
};

I want the methods that are brought from a previous state to access other methods from that same state, not the current one. So here’s the trick to achieve this - binding.

If I have an initial object named target, that is not wrapped by any decorator yet, and I want to decorate it with D1, then when I archive the overridden methods, I will bind them to the object itself. If I want to add another decorator D2 on top of that, then I will bind the backed up methods to the D1 instance before archiving them:

Decorator = function(decoratedObject){
    ...
    for (decMethod in this.newMethods){
        ...
        // If the object has already been decorated before (        // methods to the top-most decorator context. Else, bind it to the decorated object.
        if (typeof decoratedObject.decoratorScope == "function"){
            this.methodsBackup[decMethod] = _.bind(decoratedObject[decMethod], decoratedObject.decoratorScope());
        } else {
            this.methodsBackup[decMethod] = _.bind(decoratedObject[decMethod], decoratedObject);
        }
        decoratedObject[decMethod] = this.newMethods[decMethod];
        ...
    }

    return decoratedObject;
};

The bind function that I’ve used is from the Underscore JS library. The third utility method, removeDecorator, will just return the current context (decorator instance).

There are a couple of other methods that are needed to make it all work - decoratorScope, which will return the decorator instance, and of course - demoveDecorator:

Decorator = function(decoratedObject){
    ...
    return decoratedObject;
};
Decorator.extend = Extend.extendMethod;
Decorator.prototype = {
    removeDecorator: function(decoratedObject, decoratorConstructor){
        ...
    },

    overriddenMethod: function(methodName){
        ...
    },

    decoratorScope: function(){
        ...
    }
    ...
};

This all starts too look complicated. In fact, it’s too complicated to be called a design pattern. It’s a Decorator library, because one critical aspect of a design pattern candidate is to be simple enough to be easily explained. Well, as long as the client code is simple enough, it’s still useful. Here’s a possible usage:

var target,
    DemoDecorator, DemoDecorator2;

target = {
    demo: function(){
        return "not decorated";
    }
};

/* Client code: */
DemoDecorator = Decorator.extend({
    constructor: function(decoratedObject){
        decoratedObject = Decorator.apply(this, arguments);
        return decoratedObject;
    },
    newMethods: {
        demo: function(){
            var oldDemoFn = this.overriddenMethod('demo');
            return (oldDemoFn ? oldDemoFn() : '') + ' : decorated (DemoDecorator)';
        }
    }
});

DemoDecorator2 = Decorator.extend({
    constructor: function(decoratedObject){
        decoratedObject = Decorator.apply(this, arguments);
        return decoratedObject;
    },
    newMethods: {
        demo: function(){
            var oldDemoFn = this.overriddenMethod('demo');
            return (oldDemoFn ? oldDemoFn() : '') + ' : decorated (DemoDecorator II)';
        }
    }
});

I’ve used the Backbone JS inheritance mechanism (extracted to a utility script), so the constructor attribute of the object that’s passed in will contain the constructor function that will be used. The newMethods contains the methods that will be added or that will override the existing ones when the target is decorated.

I don’t really like the fact that I’m sticking utility methods on the decorated object. It’s a bad idea to modify objects that you don’t own. They might interact in an unexpected way with the client code logic that does not expect them to be there, and their names might clash with names for existing methods.

The removeDecorator utility method can get passed a decorator constructor as a parameter, and will search the whole chain, from outermost to innermost decorator, and remove the first occurrence. If no decorator is passed in, it will just assume you want the outermost one removed. Basically, it “visits” one decorator instance at a time, and checks whether the next decorator instance down the line is to be removed (is an instance of the Decorator that was passed in). If so, it grabs it’s methodsBackup property and stores on the current decorator instance. This is enough to ensure that the correct decorator instance is now next in chain.

The code is on github (code above is for this commit), including unit testing. In an article that will follow, I will explore removing dynamically added functionality with the Chain of Responsibility design pattern.

Leave a Reply