// Остальные методы просто вызывают соответствующие
// методы объекта 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.
* Элементами этого множества являются все значения, которые не являются
* элементами некоторого другого множества. Поскольку это множество
* определяется в терминах другого множества, оно не доступно для записи,
* а так как оно имеет бесконечное число элементов, оно недоступно для перечисления.
* Все, что позволяет этот класс, - это проверить принадлежность к множеству.
* Обратите внимание, что для определения этого подкласса используется метод