9.7.2. Вызов конструктора и методов базового класса

We use cookies. Read the Privacy and Cookie Policy

Класс SingletonSet из предыдущего раздела определяет совершенно новый тип множеств и полностью переопределяет основные методы, наследуемые от суперкласса. Однако часто при определении подкласса необходимо лишь расширить или немного изменить поведение методов суперкласса, а не заменить их полностью. В этом случае конструктор и методы подкласса могут вызывать конструктор и методы базового класса.

Пример 9.13 демонстрирует применение этого приема. Он определяет подкласс NonNullSet класса Set: тип множеств, которые не могут содержать элементы со значениями null и undefined. Чтобы исключить возможность включения в множество таких элементов, класс NonNullSet должен выполнить в методе add() проверку значений добавляемых элементов на равенство значениям null и undefined. Но при этом не требуется включать в класс полную реализацию метода add() - можно просто вызвать версию метода из суперкласса. Обратите также внимание, что конструктор NonNullSet() тоже не реализует все необходимые операции: он просто передает свои аргументы конструктору суперкласса (вызывая его как функцию, а не как конструктор), чтобы конструктор суперкласса мог инициализировать вновь созданный объект.

Пример 9.13. Вызов из подкласса конструктора и метода базового суперкласса

/*

 * NonNullSet - подкласс класса Set, который не может содержать элементы

 * со значениями null и undefined.

*/

function NonNullSet() {

  // Простое обращение к суперклассу.

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

  // объекта, который был создан вызовом этого конструктора.

  Set.apply(this, arguments);

}

// Сделать класс NonNullSet подклассом класса Set:

NonNullSet.prototype = inherit(Set.prototype);

NonNullSet.prototype.constructor = NonNullSet;

// Чтобы исключить возможность добавления значений null и undefined,

// достаточно переопределить метод add()

NonNullSet.prototype.add = function() {

  // Проверить наличие аргументов со значениями null и undefined

  for(var і = 0; і < arguments.length; i++)

    if (arguments[i] == null)

      throw new Еrror("Нельзя добавить null или undefined в NonNullSet”);

  // Вызвать метод базового суперкласса, чтобы фактически добавить элементы

  return Set.prototype.add.apply(this, arguments);

};

Теперь обобщим понятие "множество без пустых элементов" до понятия "фильтрованное множество": множество, элементы которого должны пропускаться через функцию-фильтр перед добавлением. Определим фабричную функцию (подобную функции enumeration) из примера 9.7), которая будет получать функцию-фильтр и возвращать новый подкласс класса Set. В действительности можно пойти еще дальше по пути обобщений и определить фабричную функцию, принимающую два аргумента: наследуемый класс и функцию-фильтр, применяемую к методу add(). Новой фабричной функции можно было бы дать имя filteredSetSubclass() и использовать ее, как показано ниже:

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

var StringSet = filteredSetSubclass(Set,

          function(x) {return typeof x===*'string";});

// Определить класс множеств, которые не могут содержать значения null,

// undefined и функции

var MySet = filteredSetSubclass(NonNullSet,

          function(x) {return typeof x !== "function";});

Реализация этой фабричной функции приводится в примере 9.14. Обратите внимание, что эта функция вызывает метод и конструктор базового класса подобно тому, как это реализовано в классе NonNullSet.

Пример 9.14. Вызов конструктора и метода базового класса

/*

* Эта функция возвращает подкласс указанного класса Set и переопределяет

* метод add() этого класса, применяя указанный фильтр.

*/

function filteredSetSubclass(superclass, filter) {

  var constructor = function() { // Конструктор подкласса

    superclass.apply(this, arguments); // Вызов конструктора базового класса

  };

  var proto = constructor.prototype = inherit(superclass.prototype);

  proto.constructor = constructor; proto.add = function() {

    // Примерить фильтр ко всем аргументам перед добавлением

    for(var і = 0; і < arguments.length; i++) {

      var v = arguments[i];

      if (!filter(v)) throw("значение + v + отвергнуто фильтром");

    }

    // Вызвать реализацию метода add из базового класса

    superclass.prototype.add.apply(this, arguments);

  };

  return constructor;

}

В примере 9.14 есть один интересный момент, который хотелось бы отметить. Он заключается в том, что, обертывая операцию создания подкласса функцией, мы получаем возможность использовать аргумент superclass в вызовах конструктора и метода базового класса и избежать указания фактического имени суперкласса. Это означает, что в случае изменения имени суперкласса достаточно будет изменить имя в одном месте, а не отыскивать все его упоминания в программном коде. Такого способа стоит придерживаться даже в случаях, не связанных с определением фабричных функций. Например, с помощью функции-обертки можно было бы переписать определение класса NonNullSet и метода Function.prototype.extend() (пример 9.11), как показано ниже:

var NonNullSet = (function() { // Определить и вызвать функцию

  var superclass = Set; // Имя суперкласса указывается в одном месте,

  return superclass.extend(

    function() { superclass.apply(this, arguments); }, // конструктор

    { // методы

      add: function() {

        // Проверить аргументы на равенство null или undefined

        for(var і = 0; і < arguments.length; i++)

          if (arguments[i] == null)

            throw new Еrror("Нельзя добавить null или undefined");

        // Вызвать метод базового класса, чтобы выполнить добавление

        return superclass.prototype.add.apply(this, arguments);

      }

    });

}());

В заключение хотелось бы подчеркнуть, что возможность создания подобных фабрик классов обусловлена динамической природой языка JavaScript. Фабрики классов представляют собой мощный и гибкий инструмент, не имеющий аналогов в языках, подобных Java и C++.