删除对对象方法的公共访问

Posted

技术标签:

【中文标题】删除对对象方法的公共访问【英文标题】:removing public access to methods on an object 【发布时间】:2011-06-30 19:04:17 【问题描述】:

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

即我在内部有一个带有 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 引用不应中断任何功能。原型链不应中断。

这样的函数的直观名称是什么? 我当前的实现是否有任何我应该注意的缺陷?

【问题讨论】:

你需要的基本上是一个克隆函数,如果它们通过某个过滤器,它只会将原始对象中的属性复制到克隆对象。 @DaveO 是的,并且在属性/方法中对this 的任何引用都需要指向原始对象而不是克隆对象。 @Raynos 这个要求让这变得更加困难。请记住,您只能删除直接附加到源对象的属性。您不能从原型中删除属性,因为它们也会在原始对象中丢失。 @DaveO 我已经得出结论,原型必须保持公开。您可以做的最好的事情是创建具有相同名称的新属性并将它们设置为undefined,以便您隐藏原型方法 _.bindAll(obj, _.functions(obj)); 行没有任何用处。 【参考方案1】:

如果您只想将对象中的某些方法公开并保持其余的私有,您可以这样做(在下面的示例中,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
    ; 
  


【讨论】:

我考虑过这个,但这需要每个构造函数定义一个readOnly 方法。这不是我想要强制执行的事情。如果我想要两种不同类型的只读对象怎么办?一个只有 setName,一个有 setName & privateMethod3【参考方案2】:

也许:

var yourfunction = function() 
   var that = ;

   var constructor = function() 
   ;

   //private methods
   var privateMethod = function() 
   ;

   constructor();

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

   return that;

【讨论】:

-1 完全没有抓住重点。关键是我希望能够拥有多个对象,每个对象具有可变数量的“公共”方法。【参考方案3】:

我想说与您的情况非常匹配的模式是Proxy。

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

【讨论】:

也许吧。请注意,要求是“代理”与真实对象无法区分。以及“代理”只能转发不做自己的变异或计算的方法。 Delegate 和 Facade 的组合是更好的模式。 没有说它不好,但是正如您指出的那样,Proxy 模式意味着接口与代理对象完全相同,即 不是 什么发帖人正在努力实现,他们想提供一个子集的界面。真正的Proxy 模式只是一个有限制的专用Delegation 模式。【参考方案4】:

您正在寻找的是 javascript 中的模块模式。它与 Gobhi 所描述的非常接近,但通常它是一个自执行函数。

详情可以在这里找到:

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

和:

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

【讨论】:

你误会了。我知道什么是模块模式,这不是我想要的。我正在寻找对象上的某种“代理”或某种可以生成具有不同可见性层的对象的工厂。一个对象实现了一个接口,我想要一个函数,它允许我获取一个对象并让它实现其当前接口的子集。【参考方案5】:

您应该做的是使用仅包含您想要保留的方法的Facade Pattern 和Delegation Pattern 将这些方法委托给原始对象的实例。借助 Javascript 提供的丰富反射,您应该能够相当轻松地以编程方式生成这些 Facade。

【讨论】:

门面也需要是代理。它必须看起来是同一类型。或者更好的是,原始对象必须看起来是返回的外观/代理的超集。但是你是正确的委派在这里很重要。事实上,委托是在函数式代码中使用的一种很好的继承模式。 a ProxyDelegation 的一个特例,它限制接口完全像它所委托的那样,并且它必须通过所有未更改的数据。因此,根据定义,如果它需要更改接口,则它不是Proxy,这正是 OP 的意图。【参考方案6】:

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

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

对评论 1 的回应

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

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

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

Google 使用以下方法(GMaps、GMail 等)。

【讨论】:

1.我希望 instanceof 检查工作。 2.我想要有可扩展性的空间。现在 2 或 3、3。所有对象。 4. 这相关吗? 5.没有。 您真的打算锁定所有对象吗?为什么为什么为什么?我以为你想隐藏一些公共对象。如果所有对象都是公开的,您应该重新检查您的应用程序设计。或者你有 10k 个公共对象?【参考方案7】:

您的实施中至少存在一个问题是对obj.constructor 的依赖。众所周知,constructor 属性是只读的,很容易搞砸。考虑以下模式,这是在 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。)


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

【讨论】:

问题是,当密钥被删除时,您可以访问这些方法。 另外主要的问题是你不能在getA里面调用this.setA,这是一个要求。 在我的示例中,您不需要调用setA,因为setAgetA 在同一个闭包内引用了变量agetA 可以直接赋值给a。如果您需要更复杂的setA 方法,您可以引用闭包中的函数而不是变量(例如定义私有函数_setA,将其包装在公共setA 方法中,然后删除setA稍后)。 但我看到了对this 访问的担忧。现在调查一下。【参考方案8】:

我认为 @nrabinowitz 的 getRestricted (link) 函数几乎就是您要寻找的答案。

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

【讨论】:

【参考方案9】:

如果你想让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的值。

【讨论】:

以上是关于删除对对象方法的公共访问的主要内容,如果未能解决你的问题,请参考以下文章

匿名对象 封装(private)this关键词构造方法

面向对象三大特性总结

Java封装 概述

Java的修饰符

面向对象_01

如何允许 cognito 身份验证的用户获得对 s3 存储桶的公共访问权限