9.7.1. Определение подкласса

В языке JavaScript объекты наследуют свойства (обычно методы) от объекта-прототипа своего класса. Если объект O является экземпляром класса В, а класс В является подклассом класса А, то объект O также наследует свойства класса А. Добиться этого можно за счет наследования объектом-прототипом класса В свойств объекта-прототипа класса А, как показано ниже, с использованием функции inherit() (пример 6.1):

В.prototype = inherit(A.prototype); // Подкласс наследует суперкласс

В.prototype.constructor = В; // Переопределить унаследованное св. constructor

Эти две строки являются ключом к созданию подклассов в JavaScript. Без них объект-прототип будет обычным объектом - объектом, наследующим свойства от Object.prototype, - а это означает, что класс будет подклассом класса Object, подобно всем остальным классам. Если добавить эти две строки в функцию defineClass() (раздел 9.3), ее можно будет преобразовать в функциюdefineSubclass() и в метод Function.prototype.extend(), как показано в примере 9.11.

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

// Простая функция для создания простых подклассов

function defineSubclass(superclass, // Конструктор суперкласса

  constructor, // Конструктор нового подкласса

  methods, // Методы экземпл.: копируются в прототип

  statics) // Свойства класса: копируются в констр-р

{

  // Установить объект-прототип подкласса

  constructor.prototype = inherit(superclass.prototype);

  constructor.prototype.constructor = constructor;

  // Скопировать методы methods и statics, как в случае с обычными классами

  if (methods) extend(constructor.prototype, methods);

  if (statics) extend(constructor, statics);

  // Вернуть класс

  return constructor;

}

// To же самое можно реализовать в виде метода конструктора суперкласса

Function.prototype.extend = function(constructor, methods, statics) {

  return defineSubclass(this, constructor, methods, statics);

};

Пример 9.12 демонстрирует, как определить подкласс «вручную», без использования функции def ineSubclass(). В этом примере определяется подкласс SingletonSet класса Set. Класс SingletonSet представляет специализированное множество, доступное только для чтения и состоящее из единственного постоянно элемента.

Пример 9.12. SingletonSet: простой подкласс множеств

// Функция-конструктор

function SingletonSet(member) {

  this.member = member; // Сохранить единственный элемент множества

}

// Создает объект-прототип, наследующий объект-прототип класса Set.

SingletonSet.prototype = inherit(Set.prototype);

// Далее добавляются свойства в прототип.

// Эти свойства переопределяют одноименные свойства объекта

Set.prototype. extend(SingletonSet.prototype, {

  // Установить свойство constructor

  constructor: SingletonSet,

  // Данное множество доступно только для чтения: методы add() и remove()

  // возбуждают исключение

  add: function() { throw "множество доступно только для чтения"; },

  remove: function() { throw "множество доступно только для чтения"; },

  // Экземпляры SingletonSet всегда имеют размер, равный 1

  size: function() { return 1; },

  // Достаточно вызвать функцию один раз и передать ей единственный элемент,

  foreach: function(f, context) { f.call(context, this.member); },

  // Метод contains() стал проще: такая реализация пригодна только

  // для множества с единственным элементом

  contains: function(x) { return х === this.member; }

});

Класс SingletonSet имеет очень простую реализацию, состоящую из пяти простых методов. Этот класс не только реализует пять основных методов класса Set, но и наследует от своего суперкласса такие методы, как toString(), toArray() и equals(). Возможность наследования методов является одной из основных причин определения подклассов. Метод equals() класса Set (определен в разделе 9.6.4), например, может сравнивать любые экземпляры класса Set, имеющие методы size() и foreach(), с любыми экземплярами класса Set, имеющими методы size() и contains(). Поскольку класс SingletonSet является подклассом класса Set, он автоматически наследует его метод equals() и не обязан иметь собственную реализацию этого метода. Безусловно, учитывая чрезвычайно упрощенную структуру множества, содержащего единственный элемент, можно было бы реализовать для класса SingletonSet более эффективную версию метода equals():

SingletonSet.prototype.equals = function(that) {

  return that instanceof Set && that.size()==1 && that.contains(this.member);

};

Обратите внимание, что класс SingletonSet не просто заимствует список методов из класса Set: он динамически наследует методы класса Set. Если в Set.prototype добавить новый метод, он тут же станет доступен всем экземплярам классов Set и SingletonSet (в предположении, что класс SingletonSet не определяет собственный метод с таким же именем).