removing public access to methods on an object

546 views Asked by At

I would like to take an object and remove some methods from it.

i.e. I have internally have an object with getter/setters on it and I want to give external users access to it. I don't want them to have access to the setter functions.

I don't want to change original object reference by removing methods from it but create a new object reference that points to the same object but has less methods on it.

  • How would I go about doing this?
  • Is this a design-pattern?
  • Are there well known solutions for these kinds of problems?

I have an implementation of this function

var readOnly = function(obj, publicData) {
    // create a new object so that obj isn't effected
    var object = new obj.constructor;
    // remove all its public keys
    _.each(object, function(val, key) {
        delete object[key];    
    });
    // bind all references to obj
    _.bindAll(obj);
    // for each public method give access to it
    _.each(publicData, function(val) {
        object[val] = obj[val];    
    });
    return object;
};

See live example, _.each _.bindAll

For all intended purposes the object returned should be the same as the original object except some of the methods aren't there anymore. The internal this reference should not break in any of the functions. The prototype chains should not break.

  • What would be an intuitive name for such a function?
  • Are there any pitfalls with my current implementation that I should be aware of?
9

There are 9 answers

2
AudioBubble On BEST ANSWER

What you should do is use a Facade Pattern that only has the methods you want to keep and Delegation Pattern to delegate those methods to an instance of the original object. With the rich reflection that is available with Javascript you should be able to generate these Facades programmaticlly fairly easily.

3
Simon Groenewolt On

I'd say that the pattern that matches your situation quite closely is the Proxy.

UPDATE: As the comments indicate, a Proxy should support the same interface as the real object and therefore is not a good match to the problem stated in the question. At the bottom of the wikipedia article on Proxy I found a link to an interesting article that compares Proxy, Adapter and Facade patterns.

2
jrumbinas On

You are choosing very complicated approach. Provide more details first:

  • Does it really really necessary to extend original object prototype (aka how many times you change object prototype in run time)?
  • How many different object are you going to create? By saying different I mean: created from the same object type, but with different public methods.
  • How many object types are you planing to lock?
  • How often are you planing to update your code?
  • Are you using any kind of minifier/compiler?

Response to comment 1:

  • If you are lock all objects then it is a matter of time when you deal performance problems (especially with IE).
  • To date you have 2-3 object types you want to lock. Wouldn't it be easier to prepare these 2-3 objects in Delegation pattern? Remember that you have to pay the price for flexibility;
  • Try using Closure compiler in advanced mode. When you leverage this tool you gonna learn how to obfuscate object properties and leave public interface. Plus every code change and recompile results in new hidden variable/function names so good luck trying to guess witch one is setA or getA function.
  • And the last but no least. Try to keep your public interface as neat and small as possible. This means: wrap up all your code with anonymous function and disclose as few objects/methods/properties as you can.

A little bit more about Closure compiler obfuscation works.

  1. Compile renames all object properties to: aa, ab, ac, etc.;
  2. You disclose public methods/properties: a.prototype.getA = a.prototype.aa; (Internally you use a.prototype.aa method. This means that public method can be replaced with any value - this has no effect to your code);
  3. And finally You disclose object: window['foo'] = new a;

The following method is used by Google (GMaps, GMail and etc.).

1
alexl On

maybe:

var yourfunction = function() {
   var that = {};

   var constructor = function() {
   };

   //private methods
   var privateMethod = function() {
   };

   constructor();

   //public methods   
   that.publicMethod = function() {
   };

   return that;
}
0
johnhunter On

I thought @nrabinowitz's getRestricted (link) function was pretty much the answer you were looking for.

I'm not a big fan of GOF pattern application to JavaScript (there's a whole discussion in itself). But this smells to me like a Decorator since we're changing runtime behaviour on certain objects - but in reverse - a De-Decorator if you like :)

0
Cat Chen On

If you want instanceof works, we need to use inheritance, like @nrabinowitz's solution. In that solution, unwanted methods are hidden with keys set to null, and those keys are accessible to user so they might be reset by user. We can prevent this by hiding those keys in an intermediate object, and because it's instantiated from an intermediate class so inheritance won't break.

function restrict(original, whitelist) {
    /* create intermediate class and instantiate */
    var intermediateClass = function() {};
    intermediateClass.prototype = original;
    var intermediateObject = new intermediateClass();

    /* create restricted class and fix constructor reference after prototype replacement */
    var restrictedClass = function() {};
    restrictedClass.prototype = intermediateObject;
    restrictedClass.prototype.constructor = original.constructor;
    if (restrictedClass.prototype.constructor == Object.prototype.constructor) {
        restrictedClass.prototype.constructor = restrictedClass;
    }

    for (var key in original) {
        var found = false;
        for (var i = 0; i < whitelist.length; i++) {
            if (key == whitelist[i]) {
                if (original[key] instanceof Function) {
                    /* bind intermediate method to original method */
                    (function(key) {
                        intermediateObject[key] = function() {
                            return original[key].apply(original, arguments);
                        }
                    })(key);
                }
                found = true;
                break;
            }
        }
        if (!found) {
            /* black out key not in the whitelist */
            intermediateObject[key] = undefined;
        }
    }

    return new restrictedClass();
}

In the following example, i and j represent two ways to implement member values. One is private member in closure and the other is public member of the class.

var originalClass = function() {
    var i = 0;
    this.j = 0;

    this.getI = function() {
        return i;
    };
    this.setI = function(val) {
        i = val;
    };
}
originalClass.prototype.increaseI = function() {
    this.setI(this.getI() + 1);
};
originalClass.prototype.decreaseI = function() {
    this.setI(this.getI() - 1);
};
originalClass.prototype.getJ = function() {
    return this.j;
};
originalClass.prototype.setJ = function(val) {
    this.j = val;
};
originalClass.prototype.increaseJ = function() {
    this.setJ(this.getJ() + 1);
};
originalClass.prototype.decreaseJ = function() {
    this.setJ(this.getJ() - 1);
};

var originalObject = new originalClass();
var restrictedObject = restrict(originalObject, ["getI", "increaseI", "getJ", "increaseJ"]);

restrictedObject.increaseI();
restrictedObject.increaseJ();
console.log(originalObject.getI()); // 1
console.log(originalObject.getJ()); // 1
console.log(restrictedObject instanceof originalClass); // true

As you can see, all setters and decrease methods are hidden in the restricted object. User can only use getter or increase the value of i and j.

4
nrabinowitz On

At least one issue with your implementation is the reliance on obj.constructor. The constructor property is notoriously not read-only, and can be easily messed up. Consider the following pattern, which is a pretty common way to define classes in Javascript:

function Foo() {};
Foo.prototype = {
    myProperty: 1,
    myFunction: function() {
        return 2;
    }
};
// make an instance
var foo = new Foo();
foo instanceof Foo; // true
foo.constructor == Foo;  // false! The constructor is now Object
(new foo.constructor()) instanceof Foo; // false

I think the way to do this is to create a new class with your obj instance as the prototype. You can then block access to the old functions by adding empty keys on an object of the new class:

function getRestricted(obj, publicProperties) {
    function RestrictedObject() {};
    RestrictedObject.prototype = obj;
    var ro = new RestrictedObject();
    // add undefined keys to block access
    for (var key in obj) {
        // note: use _.indexOf instead -- this was just easier to test
        if (publicProperties.indexOf(key) < 0) {
            ro[key] = null;
        } else {
            // again, use _.isFunction if you prefer
            if (typeof obj[key]=='function') {
                (function(key) {
                    // wrap functions to use original scope
                    ro[key] = function() {
                        // basically the same as _.bind
                        return obj[key].apply(obj, arguments);
                    }
                })(key);
            }
        }
    }
    return ro;
}

function Foo() {
    var a=0;
    this.getA = function() {
        this.setA(a+1);
        return a;
    };
    this.setA = function(newa) {
        a = newa;
    }
};

// make an instance
var foo = new Foo();
foo.setA(1);
foo.getA(); // 2

// make a restricted instance
var restrictedFoo = getRestricted(foo, ['getA']);
restrictedFoo.getA(); // 3
restrictedFoo instanceof Foo; // true
try {
    restrictedFoo.setA(2); // TypeError: Property 'setA' is not a function
} catch(e) {
    "not a function";
}
// one bump here:
"setA" in restrictedFoo; // true - just set to undefined

// foo is unaffected
foo.setA(4);
foo.getA(); // 5

(This is partially based on Crockford's power constructor functions, discussed here.)


EDIT: I updated the code above to address your comments. It now looks somewhat similar to your implementation, but it's simpler and avoids the constructor issue. As you can see, references to in public functions now refer to the old object.

1
Gobhi On

If all you want to do is expose certain methods in your object as public and keep the rest private, you can do something like this (in the example below privateMethod2 is never revealed in the new "read only" object returned to the user):

function MyObject() {

  // Private member
  var name = "Bob"; 

  // Private methods
  function setName(n) { name = n; }
  function privateMethod2() { ... }
  function privateMethod3() { ... }

  // Expose certain methods in a new object
  this.readOnly() { 
    return {
      publicMethod1: setName,
      publicMethod2: privateMethod3
    }; 
  }

}
1
Greg On

What you are looking for is called the module pattern in JavaScript. It's pretty close to what Gobhi describes but generally it is a self executing function.

Details can be found here :

http://yuiblog.com/blog/2007/06/12/module-pattern/

and :

http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth