русс | укр

Языки программирования

ПаскальСиАссемблерJavaMatlabPhpHtmlJavaScriptCSSC#DelphiТурбо Пролог

Компьютерные сетиСистемное программное обеспечениеИнформационные технологииПрограммирование

Все о программировании


Linux Unix Алгоритмические языки Аналоговые и гибридные вычислительные устройства Архитектура микроконтроллеров Введение в разработку распределенных информационных систем Введение в численные методы Дискретная математика Информационное обслуживание пользователей Информация и моделирование в управлении производством Компьютерная графика Математическое и компьютерное моделирование Моделирование Нейрокомпьютеры Проектирование программ диагностики компьютерных систем и сетей Проектирование системных программ Системы счисления Теория статистики Теория оптимизации Уроки AutoCAD 3D Уроки базы данных Access Уроки Orcad Цифровые автоматы Шпаргалки по компьютеру Шпаргалки по программированию Экспертные системы Элементы теории информации

Грубое определение типа


Дата добавления: 2015-07-09; просмотров: 709; Нарушение авторских прав


 

Существует старое высказывание: «Если оно ходит как утка и крякает как утка, значит, это утка!». Перевести этот афоризм на язык JavaScript довольно сложно, однако попробуем: «Если в этом объекте реализованы все методы некоторого класса, значит, это экземпляр данного класса». В гибких языках программиро_ вания со слабой типизацией, таких как JavaScript, это называется «грубым оп_ ределением типа»: если объект обладает всеми свойствами класса X, его можно рассматривать как экземпляр класса X, даже если на самом деле этот объект не был создан с помощью функции_конструктора X().1

 

Грубое определение типа особенно удобно использовать для классов, «заимст_ вующих» методы у других классов. Ранее в этой главе демонстрировался класс Rectangle, заимствующий метод equals() у класса с именем GenericEquals. В ре_ зультате любой экземпляр класса Rectangle можно рассматривать как экземпляр класса GenericEquals. Оператор instanceof не может определить этот факт, но в на_ ших силах создать для этого собственный метод (пример 9.7).

 

Пример 9.7. Проверка факта заимствования объектом методов заданного класса

// Возвращает true, если каждый из методов c.prototype был

// заимствован объектом o. Если o – это функция, а не объект,

// вместо самого объекта o производится проверка его прототипа.

// Обратите внимание: для этой функции необходимо, чтобы методы были

// скопированы, а не реализованы повторно. Если класс заимствовал метод,

 

// а затем переопределил его, данная функция вернет значение false. function borrows(o, c) {

 

1 Термин «грубое определение типа» появился благодаря языку программирова_ ния Ruby. Точное его название – алломорфизм.


 

192 Глава 9. Классы, конструкторы и прототипы

 

// Если объект o уже является экземпляром класса c, можно вернуть true if (o instanceof c) return true;



 

// Совершенно невозможно выполнить проверку факта заимствования методов

// встроенного класса, поскольку методы встроенных типов неперечислимы.

// В этом случае вместо того, чтобы генерировать, исключение возвращается

// значение undefined, как своего рода ответ "Я не знаю".

// Значение undefined ведет себя во многом похоже на false,

 

// но может отличаться от false, если это потребуется вызывающей программе. if (c == Array || c == Boolean || c == Date || c == Error ||

 

c == Function || c == Number || c == RegExp || c == String) return undefined;

 

if (typeof o == "function") o = o.prototype; var proto = c.prototype;

for(var p in proto) {

 

// Игнорировать свойства, не являющиеся функциями if (typeof proto[p] != "function") continue;

if (o[p] != proto[p]) return false;

}

return true;

}

 

Метод borrows() из примера 9.7 достаточно ограничен: он возвращает значение true, только если объект o имеет точные копии методов, определяемых классом c. В действительности грубое определение типа должно работать более гибко: объ_ ект o должен рассматриваться как экземпляр класса c, если содержит методы, напоминающие методы класса c. В JavaScript «напоминающие» означает «имею_ щие те же самые имена» и (возможно) «объявленные с тем же количеством аргу_ ментов». В примере 9.8 демонстрируется метод, реализующий такую проверку.

 

Пример 9.8. Проверка наличия одноименных методов

// Возвращает true, если объект o обладает методами с теми же именами

// и количеством аргументов, что и класс c.prototype. В противном случае

// возвращается false. Генерирует исключение, если класс с принадлежит

// встроенному типу с методами, не поддающимися перечислению.

function provides(o, c) {

 

// Если o уже является экземпляром класса c, он и так будет "напоминать" класс c if (o instanceof c) return true;

 

// Если вместо объекта был передан конструктор объекта, использовать объект_прототип if (typeof o == "function") o = o.prototype;

 

// Методы встроенных классов не поддаются перечислению, поэтому

// возвращается значение undefined. В противном случае любой объект

// будет напоминать любой из встроенных типов.

if (c == Array || c == Boolean || c == Date || c == Error ||

 

c == Function || c == Number || c == RegExp || c == String) return undefined;

 

var proto = c.prototype;

for(var p in proto) { // Цикл по всем свойствам в c.prototype

 

// Игнорировать свойства, не являющиеся функциями if (typeof proto[p] != "function") continue;

 

// Если объект o не имеет одноименного свойства, вернуть false if (!(p in o)) return false;


 

9.7. Определение типа объекта
   

 

// Если это свойство, а не функция, вернуть false if (typeof o[p] != "function") return false;

 

// Если две функции объявлены с разным числом аргументов, вернуть false. if (o[p].length != proto[p].length) return false;

 

}

 

// Если были проверены все методы, можно смело возвращать true. return true;

}

 

В качестве примера грубого определения типа и использования метода provide() рассмотрим метод compareTo(), описанный в разделе 9.4.3. Как правило, метод compareTo() не предназначен для заимствования, но иногда бывает желательно выяснить, обладают ли некоторые объекты возможностью сравнения с помощью метода compareTo(). С этой целью определим класс Comparable:

 

function Comparable( ) {} Comparable.prototype.compareTo = function(that) {

throw "Comparable.compareTo() – абстрактный метод. Не подлежит вызову!";

}

 

Класс Comparable является абстрактным: его методы не предназначены для вы_ зова, он просто определяет прикладной интерфейс. Однако при наличии опреде_ ления этого класса можно проверить, допускается ли сравнение двух объектов:

// Проверить, допускается ли сравнение объектов o и p

 

// Они должны принадлежать одному типу и иметь метод compareTo() if (o.constructor == p.constructor && provides(o, Comparable)) {

 

var order = o.compareTo(p);

}

 

Обратите внимание: обе функции, представленные в этом разделе, borrows() и provides(), возвращают значение undefined, если им передается объект одного из встроенных типов JavaScript, например Array. Сделано это по той простой причине, что свойства объектов_прототипов встроенных типов не поддаются пе_ речислению в цикле for/in. Если бы функции не могли выполнять проверку на принадлежность встроенным типам и возвращать undefined, тогда обнаружилось бы, что встроенные типы не имеют методов, и для них всегда возвращалось бы значение true.

 

Однако на типе Array следует остановиться особо. Вспомним, что в разделе 7.8 приводилась масса алгоритмов (таких как обход элементов массива), которые прекрасно работают с объектами, не являющимися настоящими массивами, а лишь подобными им. Метод грубого определения типа можно использовать для выяснения, является ли некоторый экземпляр объектом, напоминающим массив. Один из вариантов решения этой задачи приводится в примере 9.9.

 

Пример 9.9. Проверка объектов, напоминающих массивы

function isArrayLike(x) {      
if (x instanceof Array) return true; // Настоящий массив
if (!("length" in x)) return false; // Массивы имеют свойство length

if (typeof x.length != "number") return false; // Свойство length должно быть число,

if (x.length < 0) return false; // причем неотрицательным
if (x.length > 0) {  

// Если массив непустой, в нем как минимум должно быть свойство с именем length_1


 

194 Глава 9. Классы, конструкторы и прототипы

if (!((x.length_1) in x)) return false;

}

return true;

}

 

Пример: вспомогательный метод defineClass()

 

Данная глава заканчивается определением вспомогательного метода define_ Class(), воплощающего в себе обсуждавшиеся темы о конструкторах, прототи_ пах, подклассах, заимствовании и предоставлении методов. Реализация метода приводится в примере 9.10.

 

Пример 9.10. Вспомогательная функция для определения классов

/**

* defineClass() – вспомогательная функция для определения JavaScript_классов.

 

*

* Эта функция ожидает получить объект в виде единственного аргумента.

* Она определяет новый JavaScript_класс, основываясь на данных в этом

* объекте, и возвращает функцию_конструктор нового класса. Эта функция

* решает задачи, связанные с определением классов: корректно устанавливает

* наследование в объекте_прототипе, копирует методы из других классов и пр.

*

* Объект, передаваемый в качестве аргумента, должен иметь все

* или некоторые из следующих свойств:

*

* name: Имя определяемого класса.

* Если определено, это имя сохранится в свойстве classname объекта_прототипа.

*

* extend: Конструктор наследуемого класса. В случае отсутствия будет

* использован конструктор Object(). Это значение сохранится

* в свойстве superclass объекта_прототипа.

*

* construct: Функция_конструктор класса. В случае отсутствия будет использована новая

* пустая функция. Это значение станет возвращаемым значением функции,

* а также сохранится в свойстве constructor объекта_прототипа.

*

* methods: Объект, который определяет методы (и другие свойства,

* совместно используемые разными экземплярами) экземпляра класса.

* Свойства этого объекта будут скопированы в объект_прототип класса.

* В случае отсутствия будет использован пустой объект.

* Свойства с именами "classname", "superclass" и "constructor"

* зарезервированы и не должны использоваться в этом объекте.

*

* statics: Объект, определяющий статические методы (и другие статические

* свойства) класса. Свойства этого объекта станут свойствами

* функции_конструктора. В случае отсутствия будет использован пустой объект.

 

*

* borrows: Функция_конструктор или массив функций_конструкторов.

* Методы экземпляров каждого из заданных классов будут

* скопированы в объект_прототип этого нового класса, таким образом

* новый класс будет заимствовать методы каждого из заданных классов.


 

9.8. Пример: вспомогательный метод defineClass()
   

 

* Конструкторы обрабатываются в порядке их следования, вследствие

* этого методы классов, стоящих в конце массива, могут переопределить

* методы классов, стоящих выше.

* Обратите внимание: заимствуемые методы сохраняются

* в объекте_прототипе до того, как будут скопированы свойства

* и методы вышеуказанных объектов.

* Поэтому методы, определяемые этими объектами, могут

* переопределить заимствуемые. При отсутствии этого свойства

* заимствование методов не производится.

*

* provides: Функция_конструктор или массив функций_конструкторов.

* После того как объект_прототип будет инициализирован, данная функция

* проверит, что прототип включает методы с именами и количеством

* аргументов, совпадающими с методами экземпляров указанных классов.

* Ни один из методов не будет скопирован, она просто убедится,

* что данный класс "предоставляет" функциональность, обеспечиваемую

* указанным классом. Если проверка окажется неудачной, данный метод

* сгенерирует исключение. В противном случае любой экземпляр нового класса

* может рассматриваться (с использованием методики грубого определения типа)

* как экземпляр указанных типов. Если данное свойство не определено,

* проверка выполняться не будет.

**/

function defineClass(data) {

// Извлечь значения полей из объекта_аргумента.

 

// Установить значения по умолчанию. var classname = data.name;

 

var superclass = data.extend || Object;

 

var constructor = data.construct || function( ) {}; var methods = data.methods || {};

 

var statics = data.statics || {}; var borrows;

 

var provides;

 

// Заимствование может производиться как из единственного конструктора,

// так и из массива конструкторов.

if (!data.borrows) borrows = [];

 

else if (data.borrows instanceof Array) borrows = data.borrows; else borrows = [ data.borrows ];

 

// То же для предоставляемых свойств. if (!data.provides) provides = [];

 

else if (data.provides instanceof Array) provides = data.provides; else provides = [ data.provides ];

 

// Создать объект, который станет прототипом класса. var proto = new superclass();

 

// Удалить все неунаследованные свойства из нового объекта_прототипа. for(var p in proto)

 

if (proto.hasOwnProperty(p)) delete proto[p];

 

// Заимствовать методы из классов_смесей, скопировав их в прототип. for(var i = 0; i < borrows.length; i++) {

 

var c = data.borrows[i]; borrows[i] = c;

 

// Скопировать методы из прототипа объекта c в наш прототип


 

196 Глава 9. Классы, конструкторы и прототипы

for(var p in c.prototype) {

 

if (typeof c.prototype[p] != "function") continue; proto[p] = c.prototype[p];

}

}

// Скопировать методы экземпляра в объект_прототип

 

// Эта операция может переопределить методы, скопированные из классов_смесей for(var p in methods) proto[p] = methods[p];

 

// Установить значения зарезервированных свойств "constructor",

// "superclass" и "classname" в прототипе

 

proto.constructor = constructor; proto.superclass = superclass;

 

// Свойство classname установить, только если оно действительно задано. if (classname) proto.classname = classname;

 

// Убедиться, что прототип предоставляет все предполагаемые методы. for(var i = 0; i < provides.length; i++) { // для каждого класса

var c = provides[i];

for(var p in c.prototype) { // для каждого свойства
if (typeof c.prototype[p] != "function") continue; // только методы
if (p == "constructor" || p == "superclass") continue;  

// Проверить наличие метода с тем же именем и тем же количеством

 

// объявленных аргументов. Если метод имеется, продолжить цикл if (p in proto &&

 

typeof proto[p] == "function" &&

proto[p].length == c.prototype[p].length) continue;

// В противном случае возбудить исключение

 

throw new Error("Класс " + classname + " не предоставляет метод "+ c.classname + "." + p);

}

}

 

// Связать объект_прототип с функцией_конструктором constructor.prototype = proto;

 

// Скопировать статические свойства в конструктор for(var p in statics) constructor[p] = data.statics[p];

// И в заключение вернуть функцию_конструктор

return constructor;

}

 

В примере 9.11 приводится фрагмент, который демонстрирует использование метода defineClass().

 

Пример 9.11. Использование метода defineClass()

// Класс Comparable с абстрактным методом, благодаря которому

 

// можно определить классы, "предоставляющие" интерфейс Comparable. var Comparable = defineClass({

 

name: "Comparable",

methods: { compareTo: function(that) { throw "abstract"; } }

});

 

// Класс_смесь с универсальным методом equals() для заимствования var GenericEquals = defineClass({


 

9.8. Пример: вспомогательный метод defineClass()
   

 

name: "GenericEquals", methods: {

equals: function(that) {

 

if (this == that) return true; var propsInThat = 0;

 

for(var name in that) { propsInThat++;

if (this[name] !== that[name]) return false;

}

 

// Убедиться, что объект this не имеет дополнительных свойств var propsInThis = 0;

 

for(name in this) propsInThis++;

 

// Если имеются дополнительные свойства, объекты равны не будут if (propsInThis != propsInThat) return false;

 

// Похоже, что два объекта эквивалентны.

return true;

}

}

});

 

// Очень простой класс Rectangle, который предоставляет интерфейс Comparable var Rectangle = defineClass({

 

name: "Rectangle",

 

construct: function(w,h) { this.width = w; this.height = h; }, methods: {

 

area: function() { return this.width * this.height; },

 

compareTo: function(that) { return this.area( ) _ that.area( ); }

 

},

 

provides: Comparable

});

 

// Подкласс класса Rectangle, который вызывает по цепочке конструктор своего

// надкласса, наследует методы надкласса, определяет свои методы экземпляра

 

// и статические методы и заимствует метод equals().

 

var PositionedRectangle = defineClass({ name: "PositionedRectangle", extend: Rectangle,

 

construct: function(x,y,w,h) { this.superclass(w,h); // вызов по цепочке this.x = x;

this.y = y;

 

}, methods: {

isInside: function(x,y) {

 

return x > this.x && x < this.x+this.width && y > this.y && y < this.y+this.height;

 

}

 

}, statics: {

comparator: function(a,b) { return a.compareTo(b); }

},

borrows: [GenericEquals]

});


 



<== предыдущая лекция | следующая лекция ==>
Определение типа объекта с помощью метода Object.toString() | Модули и пространства имен


Карта сайта Карта сайта укр


Уроки php mysql Программирование

Онлайн система счисления Калькулятор онлайн обычный Инженерный калькулятор онлайн Замена русских букв на английские для вебмастеров Замена русских букв на английские

Аппаратное и программное обеспечение Графика и компьютерная сфера Интегрированная геоинформационная система Интернет Компьютер Комплектующие компьютера Лекции Методы и средства измерений неэлектрических величин Обслуживание компьютерных и периферийных устройств Операционные системы Параллельное программирование Проектирование электронных средств Периферийные устройства Полезные ресурсы для программистов Программы для программистов Статьи для программистов Cтруктура и организация данных


 


Не нашли то, что искали? Google вам в помощь!

 
 

© life-prog.ru При использовании материалов прямая ссылка на сайт обязательна.

Генерация страницы за: 0.218 сек.