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

9.7.4. Иерархии классов и абстрактные классы

9.7.4. Иерархии классов и абстрактные классы

В предыдущем разделе было предложено «предпочесть композицию наследованию». Но для иллюстрации этого принципа мы создали подкласс класса Set. Сделано это было для того, чтобы получившийся подкласс был instanceof Set и наследовал полезные методы класса Set, такие как toString() и equals(). Это достаточно уважительные причины, но, тем не менее, было бы неплохо иметь возможность использовать прием композиции без необходимости наследовать некоторую определенную реализацию множества, такую как класс Set. Аналогичный подход можно было бы использовать и при создании класса SingletonSet (пример 9.12) -этот класс был определен как подкласс класса Set, чтобы унаследовать вспомогательные методы, но его реализация существенно отличается от реализации суперкласса. Класс SingletonSet - это не специализированная версия класса Set, а совершенно иной тип множеств. В иерархии классов SingletonSet должен был бы находиться на одном уровне с классом Set, а не быть его потомком.

Решение этой проблемы в классических объектно-ориентированных языках, а также в языке JavaScript заключается в том, чтобы отделить интерфейс от реализации. Представьте, что мы определили класс AbstractSet, реализующий вспомогательные методы, такие как toString(), в котором отсутствуют реализации базовых методов, таких как foreach(). Тогда все наши реализации множеств - Set, SingletonSet и FilteredSet - могли бы наследовать класс AbstractSet. При этом классы FilteredSet и SingletonSet больше не наследовали бы ненужные им реализации.

Пример 9.16 развивает этот подход еще дальше и определяет иерархию абстрактных классов множеств. Класс AbstractSet определяет только один абстрактный метод, contains(). Любой класс, который претендует на роль множества, должен будет определить хотя бы один этот метод. Далее в примере определяется класс AbstractEnumerableSet, наследующий класс AbstractSet. Этот класс определяет абстрактные методы size() and foreach() и реализует конкретные вспомогательные методы (toString(), toArray(), equals() и т.д.). AbstractEnumerableSet не определяет методы add() или remove() и представляет класс множеств, доступных только для чтения. Класс SingletonSet может быть реализован как конкретный подкласс. Наконец, в примере определяется класс AbstractWritableSet, наследующий AbstractEnumerableSet. Этот последний абстрактный класс определяет абстрактные методы add() и remove() и реализует конкретные методы, такие как union() и intersection(), использующие их. Класс AbstractWritableSet отлично подходит на роль суперкласса для наших классов Set и FilteredSet. Однако они не были добавлены в пример, а вместо них была включена новая конкретная реализация с именем ArraySet.

Пример 9.16 довольно объемен, но он заслуживает детального изучения. Обратите внимание, что для простоты создания подклассов в нем используется функция

Function.prototype.extend().

Пример 9.16. Иерархия абстрактных и конкретных классов множеств

// Вспомогательная функция, которая может использоваться для определения
// любого абстрактного метода
function abstractmethod() { throw new Error("абстрактный метод"); }
/*
 * Класс AbstractSet определяет единственный абстрактный метод, contains().
*/
function AbstractSet() {
  throw new Error("Нельзя создать экземпляр абстрактного класса");
}
AbstractSet.prototype.contains = abstractmethod;
/*
  * NotSet - конкретный подкласс класса AbstractSet.
  * Элементами этого множества являются все значения, которые не являются
  * элементами некоторого другого множества. Поскольку это множество
  * определяется в терминах другого множества, оно не доступно для записи,
  * а так как оно имеет бесконечное число элементов, оно недоступно для перечисления.
  * Все, что позволяет этот класс, - это проверить принадлежность к множеству.
  * Обратите внимание, что для определения этого подкласса используется метод
  * Function.prototype.extendO, объявленный выше.
*/
var NotSet = AbstractSet.extend(
  function NotSet(set) { this.set = set; },
  {
    contains: function(x) { return !this.set.contains(x); },
    toString: function(x) { return "~" + this.set.toString(); },
    equals: function(that) {
      return that instanceof NotSet && this.set.equals(that.set);
    }
  }
/*
 * AbstractEnumerableSet - абстрактный подкласс класса AbstractSet.
 * Определяет абстрактные методы size() и foreach() и реализует конкретные
 * методы isEmptyO. toArrayO, to[Locale]String() и equals().
 * Подклассы, реализующие методы contains(), size() и foreach(),
 * получают эти пять конкретных методов даром.
*/
var AbstractEnumerableSet = AbstractSet.extend(
  function() {
    throw new Error("Нельзя создать экземпляр абстрактного класса");
  },
  {
    size: abstractmethod,
    fоreach: abstractmethod,
    isEmpty: function() { return this.size() == 0; },
    toString: function() {
      var s = і = 0;
      this.foreach(function(v) {
          if (i++ > 0) s += ", ";
          s += v;
      });
      return s +
    },
    toLocaleString : function() {
      var s = "{", і = 0;
      this.foreach(function(v) {
        if (i++ > 0) s += ", "
          if (v == null) s += v; // null и undefined
          else s += v. toLocaleString(); // все остальные
      });
      return s +
    },
    toArray: function() {
      var a = [];
      this.foreach(function(v) { a.push(v); });
      return a;
    },
    equals: function(that) {
      if (!(that instanceof AbstractEnumerableSet)) return false;
      // Если множество that имеет другой размер, множества не равны
      if (this.size() ! = that.sizeO) return false;
      // Проверить наличие каждого элемента this в множестве that,
      try {
        this.foreach(function(v){
          if (!that.contains(v)) throw false;}
        );
        return true; // Все элементы одинаковые: множества равны.
      } catch (х) {
        if (х === false) return false; // Множества не равны
        throw х; // Повторно возбудить любое иное возникшее исключение.
      }
    }
  });
/*
* SingletonSet - конкретный подкласс класса AbstractEnumerableSet.
* Множество из единственного элемента, доступное только для чтения.
*/
var SingletonSet = AbstractEnumerableSet.extend(
  function SingletonSet(member) { this.member = member; },
  {
    contains: function(x) { return x === this.member; },
    size: function() { return 1; },
    foreach: function(f,ctx) { f.call(ctx, this.member); }
  }
/*
 * AbstractWritableSet - абстрактный подкласс класса AbstractEnumerableSet.
 * Определяет абстрактные методы add() и remove() и реализует конкретные
 * методы union(), intersection() и difference().
*/
var AbstractWritableSet = AbstractEnumerableSet.extend(
  function() {
    throw new Error("Нельзя создать экземпляр абстрактного класса");
  },
  {
    add: abstractmethod,
    remove: abstractmethod,
    union: function(that) {
      var self = this;
      that.foreach(function(v) { self.add(v); });
      return this;
    },
    intersection: function(that) {
      var self = this;
      this.foreach(function(v){
        if(!that.contains(v)) self.remove(v);
      });
      return this;
    },
    difference: function(that) {
      var self = this;
      that.foreach(function(v) { self.remove(v); });
      return this;
    }
  });
/*
 * ArraySet - конкретный подкласс класса AbstractWritableSet.
 * Представляет множество элементов как массив значений и реализует линейный
 * поиск в массиве в своем методе contains(). Поскольку алгоритм метода containsO
 * имеет сложность 0(п) вместо 0(1), данный класс следует использовать только
 * для создания относительно небольших множеств.
 * Обратите внимание, что эта реализация опирается на методы класса Array
 * indexOfO и forEach(), которые определяются стандартом ES5.
*/
var ArraySet = AbstractWritableSet.extend(
  function ArraySet() {
    this.values = [];
    this.add.apply(this, arguments);
  },
  {
  contains: function(v) {
    return this.values.indexOf(v) != -1;
  },
  size: function() {
    return this.values.length;
  },
  foreach: function(f.c) {
    this.values.forEach(f, c);
  },
  add: function() {
    for(var і = 0; і < arguments.length; i++) {
      var arg = arguments[i];
      if (Ithis.contains(arg)) this.values.push(arg);
    }
    return this;
  },
  remove: function() {
    for(var і = 0; і < arguments.length; i++) {
      var p = this.values.indexOf(arguments[i]);
      if (p == -1) continue;
      this.values.splice(p, 1);
    )
    return this;
  }
}

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


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