删除对对象上方法的公共访问权限

时间:2011-02-20 15:51:29

标签: javascript design-patterns object public-method

我想取一个对象并从中删除一些方法。

即。我内部有一个带有getter / setter的对象,我想让外部用户访问它。我不希望他们有权访问setter函数。

我不想通过从中删除方法来更改原始对象引用,但是创建一个新的对象引用,该引用指向同一个对象,但其上的方法较少。

  • 我该怎么做?
  • 这是一种设计模式吗?
  • 这些问题是否有众所周知的解决方案?

我有这个功能的实现

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;
};

请参阅live example_.each _.bindAll

出于所有预期目的,返回的对象应该与原始对象相同,除了某些方法不再存在。内部this引用不应该在任何函数中断。原型链不应该破坏。

  • 这种功能的直观名称是什么?
  • 我应该注意当前实施中是否存在任何陷阱?

9 个答案:

答案 0 :(得分:5)

你应该做的是使用只有你想要保留的方法的Facade PatternDelegation Pattern将这些方法委托给原始对象的实例。借助Javascript提供的丰富反射,您应该可以非常轻松地以编程方式生成这些Facades。

答案 1 :(得分:2)

您的实施至少有一个问题是依赖obj.constructorconstructor属性是众所周知的只读,并且很容易混淆。考虑以下模式,这是在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

我认为这样做的方法是创建一个以obj实例为原型的新类。然后,您可以通过在新类的对象上添加空键来阻止对旧函数的访问:

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

(这部分基于Crockford的幂构造函数discussed here。)


编辑:我更新了上面的代码以解决您的意见。它现在看起来有点类似于您的实现,但它更简单并且避免了constructor问题。如您所见,公共函数中的引用现在引用旧对象。

答案 2 :(得分:1)

如果你希望instanceof有效,我们需要使用继承,就像@nrabinowitz的解决方案一样。在该解决方案中,隐藏了不需要的方法,其中键设置为null,并且用户可以访问这些键,因此用户可以重置这些键。我们可以通过将这些键隐藏在中间对象中来防止这种情况,并且因为它是从中间类实例化的,所以继承不会中断。

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();
}

在以下示例中,ij表示实现成员值的两种方法。一个是关闭的私人成员,另一个是该类的公共成员。

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

如您所见,所有setter和reduce方法都隐藏在受限对象中。用户只能使用getter或增加ij的值。

答案 3 :(得分:0)

如果您要做的只是将对象中的某些方法公开为public并将其余方法保密,那么您可以执行以下操作(在下面的示例中,privateMethod2永远不会在返回给它的新“只读”对象中显示用户):

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
    }; 
  }

}

答案 4 :(得分:0)

您正在寻找的是JavaScript中的模块模式。它与Gobhi描述的非常接近,但通常它是一个自动执行的功能。

详情可在此处找到:

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

和:

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

答案 5 :(得分:0)

您选择的是非常复杂的方法。首先提供更多详细信息:

  • 是否真的有必要扩展原始对象原型(也就是在运行时更改对象原型的次数)?
  • 您要创建多少个不同的对象?通过说不同我的意思是:从相同的对象类型创建,但使用不同的公共方法。
  • 您计划锁定多少个对象类型?
  • 您计划多久更新一次代码?
  • 您使用的是任何类型的minifier /编译器吗?

对评论1的回复

  • 如果您锁定了所有对象,那么处理性能问题(尤其是IE)就属于时间问题。
  • 到目前为止,您有2-3种要锁定的对象类型。在Delegation pattern中准备这2-3个对象会不会更容易?请记住,您必须为灵活性付出代价;
  • 尝试在高级模式下使用Closure compiler。当您利用此工具时,您将学习如何模糊对象属性并保留公共接口。此外,每个代码更改和重新编译都会产生新的隐藏变量/函数名称,所以很好运,试图猜测一个是setA或getA函数。
  • 最后但并非最不重要。尽量保持公共界面尽可能整洁。这意味着:用匿名函数包装所有代码,并尽可能少地公开对象/方法/属性。

关于Closure编译器混淆的更多信息。

  1. 编译将所有对象属性重命名为:aa,ab,ac等;
  2. 您披露公共方法/属性:a.prototype.getA = a.prototype.aa; (在内部,您使用a.prototype.aa方法。这意味着公共方法可以替换为任何值 - 这对您的代码没有影响);
  3. 最后你透露了对象:window ['foo'] = new a;
  4. Google使用以下方法(GMaps,GMail等)。

答案 6 :(得分:0)

我认为@ nrabinowitz的getRestrictedlink)功能几乎就是你要找的答案。

我不是JavaScript的GOF模式应用程序的忠实粉丝(本身就有一个完整的讨论)。但是这对我来说就像一个Decorator,因为我们正在改变某些对象的运行时行为 - 但反过来 - 如果你愿意的话,还可以使用De-Decorator:)

答案 7 :(得分:-1)

也许:

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

   var constructor = function() {
   };

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

   constructor();

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

   return that;
}

答案 8 :(得分:-1)

我会说与你的情况非常接近的模式是Proxy

更新:正如评论所示,代理应该支持与真实对象相同的接口,因此不能很好地匹配问题中陈述的问题。在维基百科关于代理的文章的底部,我找到了一篇链接到一篇比较Proxy, Adapter and Facade模式的有趣文章。