Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript Module Pattern - Protected members?

Hullo! This is my first question!

I am experimenting with the module pattern promoted by Doug Crockford and others. Mostly very happy with it so far, but I am a little unsure about the best way of handling a certain inheritance pattern.

I have it boiled down to a bare bones case using cat and mammal, although my actual intention is to make objects for a tile based game in canvas.

But here is my bare bones 'animals' case using a browser alert:

var ZOO = ZOO || {};
//
ZOO.mammal = function () {
   "use strict";
   var voice = "squeak.mp3", // default mammal sound
      utter = function () {
         window.alert(this.voice);
      };
//
   // public interface
   return {
      utter: utter,
      voice: voice
   };
};
//
ZOO.cat = function () {
   "use strict";
   // hook up ancestor
   var thisCat = ZOO.mammal();
   thisCat.voice = "miaw.mp3";
   return thisCat;
};
//
var felix = ZOO.cat();
felix.utter();

What bothers me about this approach is that I have had to make voice a public property so that cat can modify it.

What I really want is something like 'protected' visibility (from Java, ActionScript etc.), so that cat can modify voice without anyone with access to felix being able to modify it.

Is there a solution?

like image 471
brennanyoung Avatar asked Jan 02 '12 17:01

brennanyoung


3 Answers

You can simulate protected visibility (visible to yourself, and child objects) by passing a blank object to your base "class" to serve as the repository for your protected properties. This will allow you to share properties through your inheritance chain, without making them public.

var ZOO = ZOO || {};

ZOO.mammal = function (protectedInfo) {
   "use strict";
   protectedInfo = protectedInfo || {};
   protectedInfo.voice = "squeak.mp3";

   // public interface
   return {
      utter: function () {
         alert(protectedInfo.voice);
      }
   };
};

ZOO.cat = function () {
   "use strict";

   var protectedInfo = {};
   // hook up ancestor
   var thisCat = ZOO.mammal(protectedInfo);

   protectedInfo.voice = "miaw.mp3";
   return thisCat;
};

Here's a live demo

like image 94
Adam Rackis Avatar answered Sep 19 '22 19:09

Adam Rackis


Sidesteping non-answer:

There are some ways to kind of get protected properties in Javascript but they aren't necessarily very idiomatic. If I were you I would first strongly consider either

  1. Using the convention of public properties prefaced with an underscore (ex.: _voice) to denote privacy. Its very simple and is something of a standard among dynamic languages.

  2. Seek an alternate solution without inheritance. Inheritance often complicates and couples stuff to much, hence the old "prefer composition over inheritance" mantra. Javascript has many features, like duck typing and higher order functions, that often let you avoid using inheritance in situations where you would normaly need it in Java

like image 20
hugomg Avatar answered Sep 19 '22 19:09

hugomg


There is a workaround to simulate protected members, where you make public those members for a while, and then you privatise them again. I'm not a big fan of this, but it's a "solution".

I'm just quoting from this SitePoint article:

Adding Protected Members

Splitting a script into multiple modules is a common and convenient practice. It makes a large codebase much easier to manage, and allows for bandwidth savings to be made when modules aren’t always required.

But what if we want to share data between different modules? If we make that data public then we’ll lose the benefits of privacy, but if we make it private it will only be available to one module. What we really need are shared private members, and these are known as protected.

JavaScript doesn’t have protected members as such, but we can effectively create them by making data temporarily public. To achieve this, let me first introduce you to two key functions — extend and privatise — which we’ll define as part of a utility-functions object:

var utils = {
  extend : function(root, props) {
    for(var key in props) {
      if(props.hasOwnProperty(key)) {
        root[key] = props[key];
      }
    } return root;
  },
  privatise : function(root, prop) {
    var data = root[prop];
    try { delete root[prop]; } catch(ex) { root[prop] = null; }
    return data;
  }
};

The extend function simply adds new properties to an object, while the privatise function copies a property and then deletes the original. We can use extend in one module to create a public reference to a private variable, and then use privatise in another module to copy it back to a private variable and delete the public reference.

So here’s an example of the first module which has two protected members (including the utils object itself), and one public member. To keep the code example short, the utility functions are just empty shells, but they would be identical to the functions I showed you a moment ago:

var MyModule = (function() {
  var myProtectedData = 909;
  var utils = {
    extend : function(root, props) { },
    privatise : function(root, prop) { }
  };
  this.myPublicData = 42;
  return utils.extend(this, { myProtectedData : myProtectedData, utils : utils });
})();

You can see how we’re using a variant of the revealing module pattern, to return not just the public members, but the protected members as well. So at this point we have three public members: MyModule.myProtectedData, MyModule.utils and MyModule.myPublicData.

Now here’s an example of the last module which uses the privatise function to copy the specified public members back to private variables, and then delete their public references:

var MyModule = (function() {
  var myProtectedData = this.utils.privatise(this, 'myProtectedData');
  var utils = this.utils.privatise(this, 'utils');
  return this;
}).apply(MyModule);

And once that’s done the protected members are locked inside their objects, privately available to both the modules, but no longer available from outside them.

Note that the privatise function relies on having separate arguments for the object and the property-key, because objects in JavaScript are passed by reference. So root is a reference to MyModule, and when we delete a property from it that’s specified by key, we’re deleting that property from the referenced object.

But if it was like this:

privatise : function(root) {
  var data = root;
  try { delete root; } catch(ex) { root = null; } return data;
}

And called like this:

var myProtectedData = this.utils.privatise(this.myProtectedData);

Then the public members would not be deleted — the function would simply delete the reference, not the property it refers to.

The try ... catch construct is also necessary for older IE versions, in which delete is not supported. In that case we nullify the public property rather than deleting it, which is obviously not the same, but has an equivalent end result of negating the member’s public reference.

like image 23
nikoskip Avatar answered Sep 20 '22 19:09

nikoskip