Беларусь, рогачёв |
Классы
Создание приватных полей
Как вы, наверное, помните, даже с помощью недокументированных приемов нам не удается полностью скрыть поля (или методы ) от воздействия внешних функций. В результате мы не можем быть уверены, что человек, использующий наши классы, не изменит там какое-нибудь поле (а потом все будут долго искать ошибку). Вывод: во многих случаях полезно защитить наши поля от неверного использования извне. Применение контекста вызова как места хранения данных поможет нам и в этом случае. Следуя по стопам Гролео, рассмотрим три различных варианта. Первый вариант: функции 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 ), сама не обязана быть приватной (хотя мы ее и сделали таковой). Главное, чтобы она была сгенерирована прямо внутри конструктора.