Опубликован: 23.12.2005 | Уровень: специалист | Доступ: платный | ВУЗ: Московский физико-технический институт
Лекция 6:

Классы

Создание приватных полей

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

алексеи федорович
алексеи федорович
Беларусь, рогачёв
Тамара Ионова
Тамара Ионова
Россия, Нижний Новгород, НГПУ, 2009