Книга: JavaScript. Подробное руководство, 6-е издание

9.3. Классы в стиле Java

9.3. Классы в стиле Java

Если вам приходилось программировать на языке Java или других объектно-ориентированных языках со строгим контролем типов, вы, возможно, привыкли считать, что классы могут иметь четыре типа членов:

Поля экземпляра

Это свойства, или переменные экземпляра, хранящие информацию о конкретном объекте.

Методы экземпляров

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

Поля класса

Это свойства, или переменные, всего класса в целом, а не конкретного экземпляра.

Методы класса

Методы всего класса в целом, а не конкретного экземпляра.

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

Объект-конструктор

Как уже было отмечено, функция-конструктор (объект) в языке JavaScript определяет имя класса. Свойства, добавляемые в этот объект конструктора, играют роль полей класса и методов класса (в зависимости от того, является ли значение свойства функцией или нет).

Объект-прототип

Свойства этого объекта наследуются всеми экземплярами класса, при этом свойства, значениями которых являются функции, играют роль методов экземпляра класса.

Объект экземпляра

Каждый экземпляр класса - это самостоятельный объект, а свойства, определяемые непосредственно в экземпляре, не являются общими для других экземпляров. Свойства экземпляра, которые не являются функциями, играют роль полей экземпляра класса.

Процесс определения класса в языке JavaScript можно свести к трем этапам. Во-первых, написать функцию-конструктор, которая будет определять свойства экземпляра в новом объекте. Во-вторых, определить методы экземпляров в объекте-прототипе конструктора. В-третьих, определить поля класса и свойства класса в самом конструкторе. Этот алгоритм можно упростить еще больше, определив простую функцию defineClass(). (В ней используется функция extend() из примера 6.2 с исправлениями из примера 8.3):

// Простая функция для определения простых классов
function defineClass(constructor, // Функция, определяющая свойства экземпляра
                     methods, // Методы экземпляров: копируются в прототип
                     statics) // Свойства класса: копируются в конструктор
{
  if (methods) extend(constructor.prototype, methods);
  if (statics) extend(constructor, statics);
  return constructor;
}
// Простейший вариант нашего класса Range
var SimpleRange =
    defineClass(function(f,t) { this.f = f; this.t = t; },
      {
        includes: function(x) { return this.f<=x && x <= this.t;},
        toString: function() { return this.f + "..." + this.t; }
      },
      { upto: function(t) { return new SimpleRange(0, t); } });

В примере 9.3 приводится более длинное определение класса. В нем создается класс, представляющий комплексные числа, и демонстрируется, как имитировать члены класса в стиле Java. Здесь все делается «вручную» - без использования функции defineClass(), представленной выше.

Пример 9.3. Complexes: Класс комплексных чисел

/*
 * Complex.js:
 * В этом файле определяется класс Complex, представляющий комплексные числа.
 * Напомню, что комплексные числа представляют собой сумму вещественной и мнимой части,
 * где множитель і в мнимой части - это квадратный корень из -1.
*/
/*
 * Функция-конструктор определяет поля экземпляра r и і
 * в каждом создаваемом экземпляре.
 * Эти поля хранят значения вещественной и мнимой частей комплексного числа:
 * они хранят информацию, уникальную для каждого объекта.
*/
function Complex(real, imaginary) {
  if (isNaN(real) || isNaN(imaginary)) // Убедиться, что аргументы - числа.
    throw new ТуреЕггог(); // Иначе возбудить исключение,
  this.r = real;         // Вещественная часть числа,
  this.і = imaginary;    // Мнимая часть числа.
}
/*
 * Методы экземпляров класса определяются как свойства-функции объекта-прототипа.
 * Методы, определяемые ниже, наследуются всеми экземплярами и обеспечивают общность
 * поведения класса. Обратите внимание, что методы экземпляров в JavaScript
 * должны использовать ключевое слово this для доступа к полям экземпляра.
*/
// Складывает комплексное число that с текущим и возвращает сумму в виде нового объекта.
Complex.prototype.add = function(that) {
  return new Complex(this.r + that.r, this.і + that.і);
};
// Умножает текущее комплексное число на число that и возвращает произведение.
Complex.prototype.mul = function(that) {
  return new Complex(this.r * that.r - this.і * that.і, this.r * that.і + this.і * that.r);
}:
// Возвращает вещественный модуль комплексного числа. Он определяется
// как расстояние до числа на комплексной плоскости от точки (0.0).
Complex.prototype.mag = function() {
  return Math.sqrt(this.r*this.r + this.i*this.i);
};
// Возвращает комплексное число с противоположным знаком.
Complex.prototype.neg = function() { return new Complex(-this.r, -this.i); };
// Преобразует объект Complex в строку в понятном формате.
Complex.prototype.toString = function() {
  return "{" + this.r + "," + this.i + '}';
};
// Проверяет равенство данного комплексного числа с заданным.
Complex.prototype.equals = function(that) {
  return that != null &&                  // должно быть определено, не равно null
  that.constructor === Complex &&         // и быть экземпляром Complex
  this.r === that.r && this.i === that.i; // и иметь те же значения.
};
/*
* Поля класса (например, константы) и методы класса определяются как свойства
* конструктора. Обратите внимание, что в методах класса вообще не используется
* ключевое слово this: они выполняют операции только со своими аргументами.
*/
// Ниже определяется несколько полей класса, хранящих предопределенные
// комплексные числа. Их имена состоят исключительно из заглавных символов,
// чтобы показать, что они являются константами.
// (В ECMAScript 5 эти свойства можно было бы сделать доступными только для чтения)
Complex.ZERO = new Complex(0,0);
Complex.ONE = new Complex(1,0);
Complex.I = new Complex(0,1);
// Следующий метод анализирует строку в формате, возвращаемом методом
// экземпляра toString, и возвращает объект Complex или возбуждает исключение ТуреЕггог.
Complex.parse = function(s) {
  try { // Предполагается, что анализ пройдет успешно
    var m = Complex._format.exec(s); // Регулярное выражение
    return new Complex(parseFloat(m[1]), parseFloat(m[2]));
  } catch (x) { // Возбудить исключение в случае неудачи
    throw new TypeError("Строка + s + "' не может быть преобразована” +
                         в комплексное число.");
  }
};
// "Частное" поле класса, используемое методом Complex.parse().
// Символ подчеркивания в его имени указывает, что оно предназначено
// для внутреннего использования и не является частью общедоступного API класса.
Complex._format = /^{([^,]+),([^}]+)}$/;

Определение класса Complex, представленное в примере 9.3, позволяет использовать конструктор, поля экземпляра, методы экземпляров, поля класса и методы класса, как показано ниже:

var с = new Complex(2,3);     // Создать новый объект с помощью конструктора
var d = new Complex(c.i.c.r); // Использовать свойства экземпляра с
c.add(d).toString();          // => "{5.5}": использовать методы экземпляров
// Более сложное выражение, в котором используются метод и поле класса
Complex.parse(c.toStringO).   // Преобразовать с в строку и обратно,
add(c.neg()).                 // сложить с числом с противоположным знаком,
equals(Complex.ZERO)          // и результат всегда будет равен нулю

Несмотря на то что язык JavaScript позволяет имитировать члены классов в стиле языка Java, тем не менее в Java существует множество особенностей, которые не поддерживаются классами в языке JavaScript. Во-первых, в методах экземпляров классов в языке Java допускается использовать поля экземпляра, как если бы они были локальными переменными, - в Java нет необходимости предварять их ссылкой this. В языке JavaScript такая возможность не поддерживается, но похожего эффекта можно добиться с помощью инструкции with (хотя это и не рекомендуется):

Complex.prototype.toString = function() {
  with(this) {
    return + r + " + і +
  }
}; 

В языке Java поддерживается возможность объявлять поля со спецификатором final, чтобы показать, что они являются константами, и объявлять поля и методы со спецификатором private, чтобы показать, что они являются частными для реализации класса и недоступны пользователям класса. В языке JavaScript эти ключевые слова отсутствуют, поэтому, чтобы обозначить частные свойства (имена которых начинаются с символа подчеркивания) и свойства, доступные только для чтения (имена которых содержат только заглавные символы), в примере 9.3 используются соглашения по именованию. Мы еще вернемся к этим двум темами ниже в этой главе: частные свойства можно имитировать с помощью локальных переменных в замыканиях (раздел 9.6.6), а возможность определения свойств-констант поддерживается стандартом ECMAScript 5 (раздел 9.8.2).

Оглавление книги


Генерация: 0.298. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз