Псевдоклассическое наследование с конфиденциальностью?

В JavaScript: "Хорошие детали" Крокфорд утверждает, что одним из недостатков использования псевдоклассического наследования является то, что он публично раскрывает переменные экземпляра.

Например:

var Ball = function(width, color) {
  this.width = width;
  this.color = color;
}

var redBall = new Ball(5, "red");
redBall.width = 12; // Changes width to 12

Теперь, если я хочу, чтобы ширина шара была закрытой?

Вот что я пробовал:

var Ball = function(width, color) {
  this.getWidth = function() { return width; }
  this.color = color;
}

var redBall = new Ball(5, "red");

Проблема с этим заключается в том, что мы все равно можем изменить this.getWidth, и там могут быть прототипы, которые полагаются на него.

Как насчет...

var Ball = function(width, color) {
  return {
    getWidth: function() { return width; },
    color: color
  }
}

var redBall = new Ball(5, "red");

Проблема заключается в том, что методы прототипа больше не имеют доступа к переменным экземпляра. Он также ближе к функциональной схеме наследования, но с большей косвенностью, используя оператор new.

Итак, как мне добиться конфиденциальности, используя псевдоклассический шаблон наследования? Возможно ли это?

Ответы

Ответ 1

Чтобы ответить на ваш вопрос; единственный способ иметь конкретных частных членов intace - иметь как члены, так и привилегированные функции (функции, которые могут обращаться к ним) в той же области. Это означает, что все они должны находиться в теле конструктора (var my private... this.myPrivileged = function() {console.log(myPrivate...) или в IIFE с закрывающим объектом, отслеживающим экземпляры и их рядовые.

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

Чтобы иметь приват на прототипе, рядовые будут разделены. Это потому, что экземпляр не известен при объявлении вашего прототипа и частных членов.

Это связано с тем, что JavaScript не имеет частного модификатора и только имитирует их путем закрытия.

Один шаблон, который может использовать прототип, например, для конкретных переменных protected, используется пример окна Crockford.

Все защищенные объекты помещаются в поле, которое может быть открыто только с помощью ключа, ключ доступен через закрытие всех элементов-прототипов, определенных в IIFE.

Поскольку экземпляр неизвестен при создании прототипа, вы должны вызывать initProtecteds из экземпляра, чтобы создавать особые защищенные члены экземпляра.

Минимальный код с примером защищенного члена экземпляра, называемого medicalHistory, используется в Animal.

function makeBox(key){
  var ret = {};
  return {
    get : function(pKey){
      if(pKey===key){
        return ret;
      }
      return false;
    }
  }
};

var Person = function(args){
  args = args || {};
  this.name = args.name || "Nameless Person";
  this.initProtecteds();
};

//using IIFE to define some members on Person.prototype
//  these members and only these members have access to 
//  the passed object key (through closures)
//  later the key is used to create a box for each instance
//  all boxes use the same key so instances of same type
//  can access each other protected members and instances
//  inheriting from Person can do so too, extending parent methods
//  will be trickier, no example for that is given in this code
(function(key){
  //private shared member
  var privateBehavior =  function(instance,args){
    //when you invoke this from public members you can pass
    //  the instance or use call/apply, when using call/apply
    //  you can refer to this as the current instance, when
    //  passing it as an argument then instance will 
    //  be the current instance
    console.log("private shared invoked");
  };
  //set default _protecteds to false so init knows
  //  it has not been initialised and needs to be shadowed
  //  with a box
  Person.prototype._protecteds=false;
  Person.prototype.getMedicalHistory = function(){
    //Maybe run some code that will check if you can access
    //  medical history, invoking a private method
    privateBehavior(this,{});
    var protectedObject  = this._protecteds.get(key);
    //if medicalHistory is an object the calling code
    //  can now mutate it
    return protectedObject.medicalHistory;
  };
  Person.prototype.hasSameDesease = function(person){
    //this Person instance should be able to see
    //  medical history of another Person instance
    return person._protecteds.get(key);
  };
  Person.prototype.getArr = function(){
    //Returns protecteds.get(key).arr so we can
    //  mutate it and see if protecteds are instance
    //  specific
    return this._protecteds.get(key).arr;
  };
  Person.prototype.initProtecteds =  function(){
    //only create box if it hasn't been created yet
    if(this._protecteds!==false)
      return;
    //use the same key for all instance boxes, one instance
    //  can now open another instance box
    this._protecteds=makeBox(key);
    //retreive the object held by the box
    var protectedObject  = this._protecteds.get(key);
    //add protected members by mutating the object held
    //   by the box
    protectedObject.medicalHistory = "something";    
    protectedObject.arr = [];
    //protectedObject is no longer needed
    protectedObject=null;
  };
}({}));
var Animal = function(){
  this.initProtecteds();
};
(function(key){
  Animal.prototype._protecteds=false;
  Animal.prototype.initProtecteds =  function(){
    if(this._protecteds!==false)
      return;
    this._protecteds=makeBox(key);
    var protectedObject  = this._protecteds.get(key);
    protectedObject.medicalHistory = "something";    
  };
}({}));
var Employee = function(args){
  //re use Person constructor
  Person.call(this,args);
};
//set up prototype part of inheritance
Employee.prototype = Object.create(Person.prototype);
//repair prototype.constructor to point to the right function
Employee.prototype.constructor = Employee;

var ben = new Person({name:"Ben"});
var nameless = new Person();
console.log(ben.getMedicalHistory());//=something
//key is in closure and all privileged methods are in that closure
//  since {} !== {} you can't open the box unless you're in the closure
//  or modify the code/set a breakpoint and set window.key=key in the closure
console.log(ben._protecteds.get({}));//=false
//One Person instance can access another instance protecteds
//  Objects that inherit from Person are same
console.log(ben.hasSameDesease(nameless));//=Object { medicalHistory="something"}
var lady = new Animal();
//An Animal type object cannot access a Person protected members
console.log(ben.hasSameDesease(lady));//=false
var jon = new Employee({name:"Jon"});
console.log(ben.hasSameDesease(jon));//=Object { medicalHistory="something"}
//making sure that protecteds are instance specific
ben.getArr().push("pushed in ben");
console.log(jon.getArr());
console.log(nameless.getArr());
console.log(ben.getArr());

Ответ 2

Это интересно рассмотреть.

Для меня (и я считаю себя студентом js), похоже, что только частные функции-члены имеют доступ к частным переменным объекта. Это связано с тем, что он создает замыкание вокруг var:

var Ball = function(width, color) {
   var width = width;
   this.color = color;
   this.getWidth=function(){return width}
   this.specialWidthCalc=function(x){ width = width + x;}
}

Таким образом, программисты могут:

  var redBall = new Ball(5, "red");
  consoloe.log( redBall.getWidth() );

  redBall.specialWidthCalc(3);
  consoloe.log( redBall.getWidth() );

Я не могу создать прототип, который имеет доступ к ширине.