📜 ⬆️ ⬇️

Another way to organize OOP in JS

Introduction


As it was recently stated in the publication in “Honest Private Properties in the Prototype” , there are two camps of JavaScript developers:


I take myself to the second camp and solve the problem by declaring the whole class in its constructor, which allows using private / public in any combination with static .

For example, Computer.js :

(function (module) { 'use strict'; var privateStaticVariable = 1; function Computer() { var privateVariable = 5; this.publicVariable = 8; Computer.publicStaticVariable = 1; this.getAnswer = function () { return privateStaticVariable + Computer.publicStaticVariable + this.publicVariable * privateVariable ; }; } module.exports = Computer; }(module)); 

')

Problem


Everything would be fine, but OOP is not limited to open / closed, therefore many heads were broken and quite a few ways were developed to solve the problem of protected properties of classes, most of which basically use agreements (again we return to pseudo-encapsulation and prefixes), a lot more and introduces its syntax. During the search, we even managed to see the library using eval () at its core, and we know:
Eval is evil!
But oddly enough, the latter was the best (in its subjective opinion) of all those considered according to the following criteria:

Study


After a brief study of the sources, it was discovered that “security” was provided by moving the private code to the child class through regular expressions, .toSource () , eval () and magic. Naturally, this engineering marvel quickly could not work, and donating privately for the sake of protected is not very interesting.

Decision


First stage

It was decided to break the head over what may be in all classes of the hierarchy and at the same time be completely inaccessible to users of this hierarchy. On the third day, my head was still lit up with the thought, just before burying the whole idea and being content with what we have: Designer parameter !

Second phase

At that, the first part of the thinking process was completed, the next step was to figure out how to hide this most additional argument from the users of the code. And language and creativity will help us with this problem:
  1. Save the class constructor.
  2. Overwriting the class constructor.
  3. Create a private variable (object).
  4. Use Function.prototype.bind.apply () on the saved constructor with the [null, savedPrivateObject] parameter.

But doing so many manual actions is a long time, and after all, a good developer is a lazy developer ( ObjectOriented.js ):

 (function () { /** * Inject protected-data object to class * @private * @param Class {Function} Class * @param protectedData {Object} Protected-data object * @return {Function} Result class */ function injectProtected(Class, protectedData) { return (function (Native) { function Overridden() { var args = Array.prototype.map.call(arguments, function (value) { return [value]; }); args.unshift(protectedData); args.unshift(null); return (new (Function.prototype.bind.apply(Native, args))()); } Overridden.prototype = new Native({}); return Overridden; }(Class)); } }()); 

“But what about inheritance?” Or the fourth stage

To solve this problem, we first need a functional to determine if our protected section is implemented; this is solved by adding a static method for the class, for example, isProtectedInjected () . Now that we can get information about the availability of the implementation, we can think about how to implement the same object in the class and all its heirs. To do this, we at least need to be able to get the original class before introducing the protected object into it, therefore we will add a static method that returns the original class. Inheritance itself is quite standard for JavaScript, with the exception of creating an additional protected data variable and embedding it in classes that require embedding.

Last stage or “add convenience”

It's time to bring the code that implements some of the features of OOP to object-oriented style. Add the injectProtected () , extend () and isProtectedInjected , getNative () stub methods to the global built-in class Function . This will help simplify your life, because after that, any class will have this set of functions.

Result


 (function (Function) { 'use strict'; /** * Check if protected-data was injected * @returns {boolean} */ Function.prototype.isProtectedInjected = function () { return false; }; /** * Inject protected-data object to class * @private * @param Class {Function} Class * @param protectedData {Object} Protected-data object * @return {Function} Result class */ function injectProtected(Class, protectedData) { return (function (Native) { function Overridden() { var args = Array.prototype.map.call(arguments, function (value) { return [value]; }); args.unshift(protectedData); args.unshift(null); return (new (Function.prototype.bind.apply(Native, args))()); } Overridden.prototype = new Native({}); Overridden.getNative = function () { return Native; }; Overridden.isProtectedInjected = function () { return true; }; return Overridden; }(Class)); } /** * Get native class without injection of protected * @returns {Function} Class */ Function.prototype.getNative = function () { return this; }; /** * Extend from @a ParentClass * @param {Function} ParentClass * @return {Function} Result class */ Function.prototype.extend = function (ParentClass) { var protectedData = {}, parent, me = this.getNative(); if (ParentClass.isProtectedInjected()) { ParentClass = injectProtected(ParentClass.getNative(), protectedData); } parent = new ParentClass(); me.prototype = parent; me.prototype.constructor = me; protectedData.parent = parent; if (me.isProtectedInjected()) { me = injectProtected(me, protectedData); } me.prototype = parent; me.prototype.constructor = me; return me; }; /** * Injects protected-data object to class * @example * function SomeClass(protectedData/\* , ... *\/) { * protectedData.protectedMethod = function () {}; * protectedData.protectedVariable = 'Access only from children and self'; * /\* ...Realization... *\/ * }.injectProtected() * @returns {Function} */ Function.prototype.injectProtected = function () { return injectProtected(this, {}); }; }(Function)); 

Usage example


Computer.js :
 (function (module) { 'use strict'; var privateStaticVariable = 1; function Computer(protectedData) { var privateVariable = 5; this.publicVariable = 8; protectedData.badModifier = 0.1; Computer.publicStaticVariable = 1; this.getAnswer = function () { return ( privateStaticVariable + Computer.publicStaticVariable + this.publicVariable * privateVariable ) * protectedData.badModifier; }; } module.exports = Computer.injectProtected(); // <- That's it! }(module)); 

FixedComputer, js :
 (function (module) { 'use strict'; var Computer = require('Computer'); function FixedComputer(protectedData) { Computer.call(this); // Super analogue protectedData.badModifier = 1; } module.exports = FixedComputer.injectProtected().extend(Computer); // <- That's it! }(module)); 

Links


Libraries:

Source: https://habr.com/ru/post/251649/


All Articles