удаление публичного доступа к методам объекта - PullRequest
8 голосов
/ 20 февраля 2011

Я хотел бы взять объект и удалить из него некоторые методы.

т.е. У меня внутри есть объект с getter / setters, и я хочу предоставить доступ к нему внешним пользователям. Я не хочу, чтобы у них был доступ к функциям сеттера.

Я не хочу изменять исходную ссылку на объект, удаляя из нее методы, но создаю новую ссылку на объект, которая указывает на тот же объект, но содержит меньше методов.

  • Как бы я поступил так?
  • Это шаблон дизайна?
  • Существуют ли хорошо известные решения для подобных проблем?

У меня есть реализация этой функции

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

См. живой пример , _.each _.bindAll

Для всех предполагаемых целей возвращаемый объект должен быть таким же, как и исходный объект, за исключением того, что некоторых методов больше нет. Внутренняя ссылка this не должна нарушаться ни в одной из функций. Цепи прототипа не должны ломаться.

  • Каким будет интуитивное имя для такой функции?
  • Есть ли какие-либо подводные камни в моей текущей реализации, о которых мне следует знать?

Ответы [ 9 ]

5 голосов
/ 23 февраля 2011

Что вам нужно сделать, это использовать Шаблон фасада , который содержит только те методы, которые вы хотите сохранить, и Шаблон делегирования , чтобы делегировать эти методы экземпляру исходного объекта. Благодаря богатому отражению, которое доступно в Javascript, вы сможете создавать эти Фасады программным способом довольно легко.

2 голосов
/ 25 февраля 2011

По крайней мере, одна проблема с вашей реализацией - это зависимость от 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

(Это частично основано на функциях конструктора степеней Крокфорда, обсуждается здесь .)


РЕДАКТИРОВАТЬ: я обновил код выше, чтобы адресовать ваши комментарии. Теперь он выглядит несколько похожим на вашу реализацию, но он проще и позволяет избежать проблемы constructor. Как видите, ссылки на публичные функции теперь ссылаются на старый объект.

1 голос
/ 26 февраля 2011

Если вы хотите, чтобы instanceof работал, нам нужно использовать наследование, например, решение @ nrabinowitz.В этом решении нежелательные методы скрываются с ключами, установленными в нуль, и эти ключи доступны пользователю, поэтому они могут быть сброшены пользователем.Мы можем предотвратить это, скрыв эти ключи в промежуточном объекте, и поскольку он создается из промежуточного класса, чтобы наследование не нарушалось.

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

В следующем примере i и j представляютдва способа реализации значений членов.Один из них является закрытым членом в закрытии, а другой - открытым членом класса.

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

Как видите, все методы установки и уменьшения скрыты в ограниченном объекте.Пользователь может использовать только геттер или увеличить значение i и j.

0 голосов
/ 25 февраля 2011

Я думал, что функция @ 1001 * ( ссылка ) @ nrabinowitz была в значительной степени ответом, который вы искали.

Я не большой поклонник шаблонного приложения GOF для JavaScript (сама по себе целая дискуссия)Но это пахнет для меня как Decorator , так как мы меняем поведение во время выполнения для некоторых объектов - но наоборот - De-Decorator, если хотите:)

0 голосов
/ 24 февраля 2011

Вы выбираете очень сложный подход. Сначала предоставьте больше деталей:

  • Действительно ли необходимо расширять исходный прототип объекта (или сколько раз вы меняете прототип объекта во время выполнения)?
  • Сколько разных объектов вы собираетесь создать? Говоря иначе, я имею в виду: созданный из одного и того же типа объекта, но с разными публичными методами.
  • Сколько типов объектов вы планируете заблокировать?
  • Как часто вы планируете обновить свой код?
  • Используете ли вы какой-либо минификатор / компилятор?

Ответ на комментарий 1 :

  • Если вы блокируете все объекты, тогда возникает проблема с производительностью (особенно в IE).
  • На сегодняшний день у вас есть 2-3 типа объектов, которые вы хотите заблокировать. Не проще ли подготовить эти 2-3 объекта в Шаблон делегирования ? Помните, что вы должны заплатить цену за гибкость;
  • Попробуйте использовать Закрывающий компилятор в расширенном режиме. Используя этот инструмент, вы научитесь запутывать свойства объекта и выходить из открытого интерфейса. Плюс каждое изменение кода и перекомпиляция приводит к появлению новых скрытых имен переменных / функций, так что удачи в попытках угадать, кто из них является функцией setA или getA.
  • И последнее, но не менее важное. Постарайтесь, чтобы ваш публичный интерфейс был как можно более аккуратным и маленьким. Это означает: оберните весь ваш код анонимной функцией и раскройте как можно меньше объектов / методов / свойств.

Немного больше о запутывании компилятором Closure.

  1. Compile переименовывает все свойства объекта в: aa, ab, ac и т.д .;
  2. Вы раскрываете открытые методы / свойства: a.prototype.getA = a.prototype.aa; (Внутренне вы используете метод .prototype.aa. Это означает, что открытый метод может быть заменен любым значением - это не влияет на ваш код);
  3. И, наконец, Вы раскрываете объект: window ['foo'] = new a;

Google использует следующий метод (GMaps, GMail и т. Д.).

0 голосов
/ 23 февраля 2011

То, что вы ищете, называется шаблоном модуля в JavaScript. Это довольно близко к тому, что описывает Гобхи, но в целом это функция, выполняемая самостоятельно

Подробности можно найти здесь:

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

и:

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

0 голосов
/ 23 февраля 2011

Я бы сказал, что шаблон, который очень близко соответствует вашей ситуации - это Proxy .

ОБНОВЛЕНИЕ: Как показывают комментарии, прокси-сервер должен поддерживать тот же интерфейс, что и реальный объект, и, следовательно, не очень хорошо соответствует проблеме, указанной в вопросе. В нижней части статьи в Википедии о Прокси я нашел ссылку на интересную статью, в которой сравниваются Прокси, Адаптер и Фасад паттерны.

0 голосов
/ 23 февраля 2011

возможно:

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

   var constructor = function() {
   };

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

   constructor();

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

   return that;
}
0 голосов
/ 20 февраля 2011

Если все, что вы хотите сделать, это представить определенные методы в вашем объекте как общедоступные, а остальные оставить закрытыми, вы можете сделать что-то подобное (в приведенном ниже примере 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
    }; 
  }

}
...