JavaScript Prototype Design Pattern


Let's continue our discussion about JavaScript Design Patterns. We've already talked about Factory and Builder pattern. Today I'll overview the Prototype pattern.

The Prototype pattern creates new objects by cloning one of a few stored prototypes. The Prototype pattern has two advantages: it speeds up the instantiation of very large, dynamically loaded classes (when copying objects is faster), and it keeps a record of identifiable parts of a large data structure that can be copied without knowing the subclass from which they were created. Have a look at the following illustration, depicting the pattern:


While there is a lot of information about cloning on the internet and even some suggest using it in the prototype design, the external approach is utterly incorrect. However since we are Object Oriented programmers, we would like to clone both public and private members. None of the external approaches will give you such result. On the other hand, if you will be willing to settle with public members cloning, might as well use parse/stringify methods combination of JSON class, which give the best results according to the cloning performance tests.

Implementation


We'll be basing our classes on JsDeepDive.Common.Manager example from Object Oriented JavaScript article, to show the effect on both public and private members:
var JsDeepDive = JsDeepDive || {};

function deepClone1(obj) {
  return JSON.parse(JSON.stringify(obj));
}

(function (key) {
 "use strict";
 JsDeepDive.PrototypedEntity = function (someParameter) {
  /* Start private parameters and functions of the class */
  var privates = {
   privateMember: undefined, 

   getPrivateMember: function getPrivateMember() {
    return this.privateMember;
   },   

   setPrivateMember: function setPrivateMember(value) {
    this.privateMember = value;
   },

   _constructor: function _constructor(someParameter) {
    this.privateMember = someParameter;
   }
  };
  /* End private parameters and functions of the class */

  this._ = function (aKey) {
   return key === aKey && privates;
  };
  privates._constructor(someParameter);        
 };

 JsDeepDive.PrototypedEntity.prototype = {
  getPrivateMember: function getPrivateMember() {
   return this._(key).getPrivateMember();
  },

  setPrivateMember: function setPrivateMember(test) {
   return this._(key).setPrivateMember(test);
  },
  publicMember: 1
 };
}({}));

var a = new JsDeepDive.PrototypedEntity(3);
a.setPrivateMember(2);
a.publicMember = 5;
console.log('a.privateMember: ' + a.getPrivateMember());
console.log('a.publicMember: ' + a.publicMember);
var b = deepClone1(a);
console.log('b.publicMember: ' + b.publicMember);
console.log('b.privateMember: ' + b.getPrivateMember());
Once you run the example, you'll encounter into error on line 53, since _ method is undefined, when called in line 36. Let's change things a bit. First we'll extend our _ method, so that we could update the privates property.
this._ = function (aKey, newPrivates) {   
 if (key !== aKey) {
  return;
 }
 if (newPrivates) {
  privates = newPrivates;
 } else {
  return privates;
 }
};
Next thing we do is to add a clone method, which will clone all public and privates using our new _ method:
clone: function clone() {
 var obj = {};
 for(var key in this) {
        obj[key] = this[key];
    }
 obj._(key, this._(key));
 return obj;
}
Now the full pattern:
var JsDeepDive = JsDeepDive || {};

function deepClone1(obj) {
  return JSON.parse(JSON.stringify(obj));
}

(function (key) {
 "use strict";
 JsDeepDive.PrototypedEntity = function (someParameter) {
  /* Start private parameters and functions of the class */
  var privates = {
   privateMember: undefined, 

   getPrivateMember: function getPrivateMember() {
    return this.privateMember;
   },   

   setPrivateMember: function setPrivateMember(value) {
    this.privateMember = value;
   },

   _constructor: function _constructor(someParameter) {
    this.privateMember = someParameter;
   }
  };
  /* End private parameters and functions of the class */

  this._ = function (aKey, newPrivates) {   
   if (key !== aKey) {
    return;
   }
   if (newPrivates) {
    privates = deepClone1(newPrivates);
   } else {
    return privates;
   }
  };
  privates._constructor(someParameter);        
 };

 JsDeepDive.PrototypedEntity.prototype = {
  getPrivateMember: function getPrivateMember() {
   return this._(key).getPrivateMember();
  },

  setPrivateMember: function setPrivateMember(test) {
   return this._(key).setPrivateMember(test);
  },
  publicMember: 1,
  clone: function clone() {
   var obj = {};
   for(var key in this) {
             obj[key] = this[key];
      }
   obj._(key, this._(key));
   return obj;
  }
 };
}({}));

var a = new JsDeepDive.PrototypedEntity(3);
a.setPrivateMember(2);
a.publicMember = 5;
console.log('a.privateMember: ' + a.getPrivateMember());
console.log('a.publicMember: ' + a.publicMember);
var b = a.clone();
console.log('b.privateMember: ' + b.getPrivateMember());
console.log('b.publicMember: ' + b.publicMember);
And the produced successful results:
a.privateMember: 2
a.publicMember: 5
b.privateMember: 2
b.publicMember: 5 
Hope you found it helpful and use this pattern in the future.

Comments

Popular posts from this blog

CAP Theorem and blockchain

Length extension attack

Contract upgrade anti-patterns