- PVSM.RU - https://www.pvsm.ru -
Как недавно было сказано в публикации в «Честные приватные свойства в прототипе» [1], существует два лагеря JavaScript-разработчиков:
Я отношу себя ко второму лагерю и решаю проблему объявлением всего класса в его конструкторе, что позволяет использовать private/public в любой комбинации с static.
Например, 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));
Всё бы хорошо, но ООП не ограничивается открытымзакрытым, посему поводу было сломано немало голов и разработано не мало способов решения проблемы защищённых свойств классов, большая часть которых в своей основе использует соглашения (опять возвращаемся к псевдо-инкапсуляции и префиксам), немалая ещё и вводит свой синтаксис. Во время поисков даже удалось увидеть библиотеку использующую eval() в своей основе, а мы знаем:
Eval is evil!
Но как ни странно, последняя была наилучшей (на свой субъективный взгляд) из всех рассмотренных по следующим критериям:
После недолгого изучения исходников было обнаружено, что «защищённость» обеспечивалась перемещением приватного кода в дочерний класс через регулярные выражения, .toSource(), eval() и магию. Естественно быстро это чудо инженерной мысли работать не могло, да и жертвовать private ради protected не очень-то интересно.
Было решено поломать голову над тем, что может быть у всех классов иерархии и в то же время быть абсолютно недоступно пользователям этой иерархии. На третий день голову-таки озарила мысль, как раз перед тем, как хоронить всю затею и довольствоваться тем, что есть: Параметр конструктора!
На этом первая часть мыслительного процесса была завершена, следующим этапом было придумать как скрывать этот самый дополнительный аргумент от пользователей кода. И с этой проблемой нам поможет язык и креатив:
Но производить столько действий вручную — долго, а ведь хороший разработчик — ленивый-разработчик(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));
}
}());
Для решения этой проблемы прежде всего нужен функционал для определения внедрена ли наша защищённая секция, это решается добавлением статического метода для класса, например, isProtectedInjected(). Теперь, когда мы можем получить информацию о наличии внедрения, мы можем подумать о том, как внедрять один и тот же объект в класс и всех его наследников. Для этого нам как минимум нужно иметь возможность получать оригинальный класс до внедрения в него защищённого объекта, посему будем добавлять статический метод возвращающий оригинальный класс. Само же наследование вполне стандартно для JavaScript, за исключением создания дополнительной переменной защищённых данных и внедрения её в классы, требующие внедрения.
Самое время привести код реализующий некоторые возможности ООП к объектно-ориентированному стилю. Добавим глобальному встроенному классу Function методы injectProtected(), extend() и методы заглушки isProtectedInjected, getNative(). Это поможет упростить себе жизнь, т.к. после этого любой класс будет иметь этот набор функций.
(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));
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 = Computer.injectProtected().extend(Computer); // <- That's it!
}(module));
Библиотеки:
Автор: saksmt
Источник [6]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/84170
Ссылки в тексте:
[1] «Честные приватные свойства в прототипе»: http://habrahabr.ru/post/251477
[2] создаёт свой синтаксис: https://github.com/oopjs/oopjs
[3] ещё одна: https://github.com/Cleod9/oopsjs
[4] та самая на основе eval(): https://code.google.com/p/oopsjs
[5] результат этой статьи: https://gist.github.com/saksmt/020e5f62e819a81f018e
[6] Источник: http://habrahabr.ru/post/251649/
Нажмите здесь для печати.