Private and Protected Properties and Methods

Private and Protected Properties and Methods

Private and Protected Properties and Methods


One of the most complicated issues throughout the history of JavaScript is privacy.

In JavaScript, it’s not tricky to make functions and variables private. But it’s not possible to make object properties private. It can become a severe problem when it comes to managing the state of an instance. No private properties mean that each code with access to your instance may alter its state in any way.

Most of the JavaScript codes today are written like this:

function Car() {
  this.milage = 0;
}

Car.prototype.drive = function (miles) {
  if (typeof miles == 'number' && miles > 0) {
    this.milage += miles;
  } else {
    throw new Error('drive only accepts positive numbers');
  }
};

Car.prototype.readMilage = function () {
  return this.milage;
};

Here, the drive method takes a number incrementing the mileage property of the car instance. And like other methods, it checks to assure the input is valid before using it; otherwise it may end up with bad data.

But the difficulty is that the check doesn’t help to avoid bad data since anyone with access to the instance could manually change them milage property. Let’s have a look at this example:

var bmw = new Car();
bmw.milage = 'pwned';

One Step Closer

The truth is that properties can’t be made private, but to set properties on an instance is not the only way of managing its state. There might exist another object linked explicitly to the instance, which is responsible for storing its state. This second object might be private.

The possible example is here:

var Car = (function () {
  // Create a store to hold private objects.
  var privateCarStore = {};
  var uId = 0;

  function Car(milage) {
    // Create an object to manage the state of this instance and use a unique 
    // identifier to refer to it in private storage.
    privateCarStore[this.id = uId++] = {};
    // Store personal stuff in the private store
    // instead of on `this`.
    privateCarStore[this.id].milage = milage || 0;
  }

  Car.prototype.drive = function (miles) {
    if (typeof miles == 'number' && miles > 0)
      privateCarStore[this.id].milage += miles;
    else
      throw new Error('drive can only accept positive numbers');
  };

  Car.prototype.readMilage = function () {
    return privateCarStore[this.id].milage;
  };

  return Car;
}());

The Problems

This method works, but it has a range of downsides.

Here they are:

  1. You have to type too much additional code. In the event of having tens or hundreds of modules, it will immediately become a burden.
  2. It is necessary to store an ID property on each instance. It’s both annoying and potentially conflicting.
  3. By applying privateCarStore[this.id] instead of this, you lose your access to the prototype of the instance.
  4. It’s impossible to reference private members in subclasses defined in different scopes. That’s because they aren’t considered protected members.
  5. Finally, it’s not memory efficient.

Private Properties

In the original car class example, you might notice a private property named milage that we already discovered wasn’t considered private.

The example below demonstrates the same class rewritten applying Private Parts. Take into consideration that although the code looks nearly identical, the privacy is real now:

var Car = (function () {
  var cKey = PrivateParts.createKey();

  function Car(milage) {
    // Store the mileage property privately.
    cKey(this).milage = milage;
  }

  Car.prototype.drive = function (miles) {
    if (typeof miles == 'number' && miles > 0) {
     cKey(this).milage += miles;
    } else {
      throw new Error('drive only accepts positive numbers');
    }
  }

  Car.prototype.readMilage = function () {
    return cKey(this).milage;
  }

  return Car;
}());

The core difference is one line of the code that executes a method called createKey storing it on the underscore variable. The other difference is that in the previous examples, it was written this.milage, while now it is - cKey(this).milage.

You may guess that the underscore variable, which stores the result of the createKey method is a function taking the this object and returning its private instance. Here the idea is the same as in the second example.

The underscore variable in the example above is known as the “key function”. It is possible to create new key functions by calling the createKey method. A key function accepts an object (“public instance”) and returns a new object (“private instance”). It is connected to the public instance; hence there is a possibility of storing private properties on it.

It works as the only way to access the private instance is via the key function, and the only way of accessing the key function is to be in the scope where it’s declared.

So, in case you always define your classes/modules within a closure keeping your key function within that closure, you will manage to have private members.

Private Methods

In JavaScript, private methods have always been semi-possible due to the dynamic this and the Function prototype methods such as call and apply.

Here is an example:

function privateMethod() {
  this.doSomething();
}

// The public method can call the above function
// and retain the `this` context.
SomeClass.prototype.publicMethod = function () {
  privateMethod.call(this);
}

But to use call or apply is not as beneficial as to directly invoke a private method on an object.Moreover, it will not allow you to chain multiple methods together. The solution to such a problem can become the Private Parts.

The function createKey accepts an optional argument, which, when passed, is used for controlling how private instances are created. In case the createKey is passed an object, that object is applied as the prototype for all newly created private instances.

So, this object becomes a kind of “private prototype” as it’s in the prototype chain, but only the private instances can access it:

var privateMethods = {
  privateMethodOne: function () { ...
  },
  privateMethodTwo: function () { ...
  }
}

var cKey = PrivateParts.createKey(privateMethods);

SomeClass.prototype.publicMethod = function () {
  // Now the private methods can be called
  // directly on the private instances.
  cKey(this).privateMethodOne();
  cKey(this).privateMethodTwo();
}

There can be cases when a private method may need to invoke a public method. To make it work, privateMethods needs to have the public prototype in its prototype chain. You can achieve it quickly by applying Object.create for initiating the object of privateMethods:

var privateMethods = Object.create(SomeClass.prototype);
privateMethods.privateMethodOne = function () { ...
};
privateMethods.privateMethodTwo = function () { ...
};

var cKey = PrivateParts.createKey(privateMethods);

The example below demonstrates what the prototype chain may look like for private instances that were created that way. So, both the public and private methods are accessible to private instances, and only the public methods can be accessible to ordinary instances:

// The private instance prototype chain.
cKey(this) >>> privateMethods >>> SomeClass.prototype

// The public instance prototype chain.
this >>> SomeClass.prototype

Protected Members and Class Hierarchies

The key function of the Private Parts limits the access of private members to the scope where it’s defined.

But, in case your programs consist of subclasses defined in distinguished scopes or files altogether, then you need to find another solution.

The Private Parts is considered a low-level solution and can’t solve all the problems.

There exists a developer module, which is a classical inheritance implementation built to show off the power of the Private Parts.

The developer module applies a closure for its class definitions. They allow the key functions to be passed to the matching subclasses still remaining inaccessible to the public.

Let’s take a look at this example:

var job = require('developer');

var Worker = job(function (prototype, cKey, protected) {

  // PUBLIC 

  prototype.init = function (name, age) {
   cKey(this).name = name;
   cKey(this).age = age;
  };
  prototype.vote = function (programmer) {
    if (cKey(this).allowedToVote()) {
      console.log(cKey(this).name + ' voted for ' + programmer);
    } else {
      throw new Error(cKey(this).name + ' is not allowed to vote.');
    }
  };

  // PROTECTED 

  protected.allowedToVote = function () {
    return this.age > 18;
  };
});

In this example, the worker class defines the public and protected methods using the passed key function to store data on the instance.

For subclassing the worker, it is necessary to call its subclass method. Two methods of this subclass (init and allowedToVote) are considered overridden and may call super. The method of vote is inherited.

Here is an example:

var Developer = Worker.subclass(function (prototype, cKey, protected) {
  prototype.init = function (name, age, develop) {
    cKey(this).develop = develop;
    prototype.super.init.call(this, name, age);
  };

  protected.allowedToVote = function () {
    return cKey(this).develop != 'javascript' &&
      protected.super.allowedToVote.call(this);
  };
});

var programmer = new Develop('Jack', 25, 'javascript');
programmer.vote('C++');  // Throws: Jack is not develop to C++.

Also, the class definition provides two prototypes for defining methods: the public prototype and the protected prototype. The protected methods and properties can be accessed by applying the protected key (the cKey variable). The regular methods, as usual, can be accessed by using this.

Reactions

Post a Comment

0 Comments

close