| Беларусь, рогачёв |
Классы
Создание приватных полей
Как вы, наверное, помните, даже с помощью недокументированных приемов нам не удается полностью скрыть поля (или методы ) от воздействия внешних функций. В результате мы не можем быть уверены, что человек, использующий наши классы, не изменит там какое-нибудь поле (а потом все будут долго искать ошибку). Вывод: во многих случаях полезно защитить наши поля от неверного использования извне. Применение контекста вызова как места хранения данных поможет нам и в этом случае. Следуя по стопам Гролео, рассмотрим три различных варианта. Первый вариант: функции getter и setter должны быть вызваны явно. Второй вариант: используем addProperty, getter и setter вызываются неявно при обращении к свойству как к обычному полю. Наконец, третьим будет вариант с перекрестным использованием приватных свойств несколькими различными методами класса. Следует заметить, что для первых двух вариантов будет применен прием, с которым мы встречались в предыдущих параграфах: добавление нужных функций в прототип одного из системных типов (в данном случае - в Object ), в результате чего все объекты этого типа смогут использовать добавленные нами методы. Как мы увидим в следующей лекции, эта возможность использования новых методов относится и к объектам производных типов.
Итак, сначала мы рассмотрим, как делаются первые два варианта приватных свойств. Правила для применяемых здесь getter 'ов и setter 'ов те же, что и при реализации статических свойств: getter принимает аргумент - значение внутреннего поля, в котором хранятся данные; setter возвращает значение, которое нужно этому внутреннему полю присвоить. Вот код, в котором для первого варианта применяется функция addExplicitPrivateProperty, а для второго - addImplicitPrivateProperty:
// Будем исследовать этой функцией объекты, чтобы
// убедиться, что приватные свойства там не спрятаны.
_global.dumpObj = function(obj){
// Снимаем "защиту" со скрытых полей
ASSetPropFlags(obj,null,0,1);
for(name in obj){
trace(name + ": " + obj[name]);
}
}
// Приватное свойство с явными getter'ом и setter'ом
Object.prototype.addExplicitPrivateProperty = function(name, getter, setter) {
var propVal;
this["get" + name] = function() {
return getter.call(this, propVal);
};
this["set" + name] = function(newVal) {
propVal = setter.call(this, newVal);
};
}
// По традиции скрываем то, что мы добавили в системные объекты
ASSetPropFlags(Object.prototype, "addExplicitPrivateProperty", 1);
// Приватное свойство с неявными getter'ом и setter'ом
Object.prototype.addImplicitPrivateProperty = function(name, getter, setter) {
var propVal;
this.addProperty(name,
function() {
return getter.call(this, propVal);
},
function(newVal) {
propVal = setter.call(this, newVal);
}
);
}
// По традиции скрываем то, что мы добавили в системные объекты
ASSetPropFlags(Object.prototype, "addImplicitPrivateProperty", 1);
// Ключевое слово var стоит на случай переноса кода в функцию
var getter = function(propVal){
return "Значение: " + propVal;
}
// Ключевое слово var стоит на случай переноса кода в функцию
var setter = function(newVal){
return newVal.toString().toUpperCase();
}
// Тестируем
a = new Object();
b = new Object();
a.addExplicitPrivateProperty("testText", getter, setter);
b.addImplicitPrivateProperty("testText", getter, setter);
// Используем отсутствие чувствительности
// идентификаторов к регистру (более точные имена
// функций - settestText и gettestText).
a.setTestText("Текст для объекта а");
trace(a.getTestText());
b.testText = "А такой текст будет в объекте b";
trace(b.testText);
trace("");
trace("----- Содержимое объекта а ------");
dumpObj(a);
trace("----------------------");
trace("");
trace("----- Содержимое объекта b ------");
dumpObj(b);
trace("----------------------");
6.1.
На выходе этот код дает:
Значение: ТЕКСТ ДЛЯ ОБЪЕКТА А Значение: А ТАКОЙ ТЕКСТ БУДЕТ В ОБЪЕКТЕ B ----- Содержимое объекта а ------ settestText: [type Function] gettestText: [type Function] __constructor__: [type Function] constructor: [type Function] __proto__: [object Object] ---------------------- ----- Содержимое объекта b ------ testText: Значение: А ТАКОЙ ТЕКСТ БУДЕТ В ОБЪЕКТЕ B __constructor__: [type Function] constructor: [type Function] __proto__: [object Object] ----------------------
Мы видим, что свойства замечательно работают, но до самих внутренних данных мы добраться не можем даже с помощью функции dumpObj (в случае объекта b свойство testText, которое мы видим - это созданное нами свойство. Это можно распознать по тому факту, что перед текстом выведен префикс "Значение:", то есть сработал getter ).
Но как быть, если у нас есть несколько разных свойств, которые мы хотим сделать приватными, но при этом к ним (именно ко внутренним данным) должны иметь доступ несколько различных функций? В этом случае Гролео рекомендует заводить как сами свойства, так и функции, которые будут с ними работать, в контексте вызова конструктора. Сейчас мы приведем пример использования такой техники. Пример будет описывать "шифрованную" строчку. Шифрование применим простейшее - циклическим сдвигом. При этом внутренние данные будут храниться в нешифрованном виде, но наружу они будут выдаваться уже зашифрованными. Также мы сделаем функцию для проверки, правильно ли расшифрована строка. Вот код этого примера:
_global.CodedStringContainer = function(baseForCoding, stringToStore){
// Секретные данные
var uncoded_str = stringToStore;
// Эти данные несекретны, но просто не нужны снаружи
var spaceCode = 32;
var maxCode = 127;
var diff = maxCode - spaceCode;
// Для использования в сгенерированной внутри приватной функции
var thisVar = this;
// Эта функция в принципе могла бы быть и публичной, нарушить
// целостность данных она не позволяет. Но, с другой стороны,
// снаружи она явно не нужна
var updateCodedString = function(){
// не забудем, что в приватной функции (которая не сохранена
// в this) ключевое слово this указывает не туда, куда обычно.
thisVar.coded_str = "";
// Кодируем
for (var i=0; i<uncoded_str.length; i++){
var charCode = uncoded_str.charCodeAt(i);
charCode = (charCode - spaceCode + baseForCoding)%diff
+ spaceCode);
thisVar.coded_str += String.fromCharCode(charCode);
}
}
// В принципе, такой интерфейс делать необязательно,
// можно удовольствоваться установкой в конструкторе
this.setUncodedString = function(str){
uncoded_str = str;
updateCodedString();
}
// Проверка - знаем ли мы "пароль".
this.isStringEqualToUncoded = function(str){
return uncoded_str === str;
}
// кодируем переданную строчку
updateCodedString();
}
_global.CodedStringContainer.prototype.getCodedString = function(){
return this.coded_str;
}
// Проверяем, что получилось
container = new CodedStringContainer(20, "MyString");
trace(container.getCodedString());
trace(container.isStringEqualToUncoded("MyString"));
trace(container.isStringEqualToUncoded("YourString"));
trace("---------------");
// Меняем строку
container.setUncodedString("YourString");
trace(container.getCodedString());
trace(container.isStringEqualToUncoded("MyString"));
trace(container.isStringEqualToUncoded("YourString"));
6.2.
В результате выполнения этого кода увидим вот что:
a.g)'}#{
true
false
---------------
m$*'g)'}#{
false
trueТак что строка шифруется, заменяется, проверяется, но ни ее, ни число, используемое для шифрования, вы посмотреть не сможете, ибо они спрятаны в контексте вызова конструктора. Еще раз подчеркнем, что предыдущие методы реализации приватных свойств нам не подошли бы, ибо сейчас нам был нужен одновременный доступ к обоим приватным свойствам (которые в данном случае являются обычными полями ). Интересно, что функция, которой необходим этот доступ (а именно, updateCodedString ), сама не обязана быть приватной (хотя мы ее и сделали таковой). Главное, чтобы она была сгенерирована прямо внутри конструктора.