Source: Memoization.js

/**
 * Surrogate Class Feature
 */
/*global dessert, troop */
(function () {
    "use strict";

    var hOP = Object.prototype.hasOwnProperty;

    /**
     * @class troop.Memoization
     * @ignore
     */
    troop.Memoization = {
        /**
         * Adds instance to registry. Must be called on class object!
         * @this {troop.Base} Troop-based class
         * @param {string} key Instance key
         * @param {troop.Base} instance Instance to be memoized
         */
        addInstance: function (key, instance) {
            this.instanceRegistry[key] = instance;
        },

        /**
         * Fetches a memoized instance from the registry.
         * @param {string} key
         * @returns {troop.Base}
         */
        getInstance: function (key) {
            var instanceRegistry = this.instanceRegistry;
            return instanceRegistry ? instanceRegistry[key] : undefined;
        },

        /**
         * Maps instance to registry
         * Receives constructor arguments
         * @returns {string} Instance key
         */
        mapInstance: function () {
            return this.instanceMapper.apply(this, arguments);
        }
    };

    troop.Base.addMethods(/** @lends troop.Base */{
        /**
         * Assigns instance key calculator to class. Makes class memoized.
         * @param {function} instanceMapper Instance key mapper function.
         * @example
         * var MyClass = troop.Base.extend()
         *     .setInstanceMapper(function (arg) {return '' + arg;})
         *     .addMethods({
         *         init: function () {}
         *     }),
         *     myInstance1 = MyClass.create('foo'),
         *     myInstance2 = MyClass.create('foo');
         * MyClass.isMemoized() // true
         * myInstance 1 === myInstance2 // true
         * @returns {troop.Base}
         */
        setInstanceMapper: function (instanceMapper) {
            dessert
                .isFunction(instanceMapper, "Invalid instance key calculator")
                .assert(!hOP.call(this, 'instanceMapper'), "Instance mapper already set");

            this
                .addMethods(/** @lends troop.Base */{
                    /**
                     * Maps constructor arguments to instance keys in the registry.
                     * Added to class via .setInstanceMapper().
                     * @function
                     * @returns {string}
                     */
                    instanceMapper: instanceMapper
                })
                .addPublic(/** @lends troop.Base */{
                    /**
                     * Lookup registry for instances of the memoized class.
                     * Has to be own property as child classes may put their instances here, too.
                     * Added to class via .setInstanceMapper().
                     * @type {object}
                     */
                    instanceRegistry: {}
                });

            return this;
        },

        /**
         * Tells whether the current class (or any of its base classes) is memoized.
         * @returns {boolean}
         * @see troop.Base.setInstanceMapper
         */
        isMemoized: function () {
            return typeof this.instanceMapper === 'function';
        },

        /**
         * Clears instance registry. After the registry is cleared, a new set of instances will be created
         * for distinct constructor arguments.
         * @returns {troop.Base}
         * @see troop.Base.setInstanceMapper
         */
        clearInstanceRegistry: function () {
            dessert.assert(hOP.call(this, 'instanceRegistry'), "Class doesn't own an instance registry");
            this.instanceRegistry = {};
            return this;
        }
    });
}());