Чтение онлайн

ЖАНРЫ

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

return this;

},

// Остальные методы просто вызывают соответствующие

// методы объекта this.set и ничего более,

remove: function {

this.set.remove.apply(this.set, arguments);

return this;

}.

contains: function(v) {
return this.set.contains(v);},

size: function { return this.set.size; },

foreach: function(f,c) { this.set.foreach(f.c); }

}

)

Одно

из преимуществ применения приема композиции в данном случае заключается в том, что требуется определить только один подкласс
FilteredSe
t. Экземпляры этого класса могут накладывать ограничения на элементы любого другого эк» земпляра множества. Например, вместо класса
NonNullSet
, представленного выше, реализовать подобные ограничения можно было бы так:

var s = new FilteredSet(new Set, function(x) { return x !== null; });

Можно даже наложить еще один фильтр на фильтрованное множество:

var t = new FilteredSet(s, { function(x) { return !(x instanceof Set); });

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.

Поделиться с друзьями: