Решение этой проблемы в классических объектно-ориентированных языках, а также в языке
<b>JavaScript</b>
заключается в том, чтобы отделить интерфейс от реализации. Представьте, что мы определили класс
<b>AbstractSet</b>
, реализующий вспомогательные методы, такие как
<b>toString(),</b>
в котором отсутствуют реализации базовых методов, таких как
<b>foreach().</b>
Тогда все наши реализации множеств -
<b>Set, SingletonSet</b>
и
<b>FilteredSet</b>
- могли бы наследовать класс
<b>AbstractSet</b>
. При этом классы
<b>FilteredSet</b>
и
<b>SingletonSet</b>
больше не наследовали бы ненужные им реализации.
Пример 9.16 развивает этот подход еще дальше и определяет иерархию абстрактных классов множеств. Класс
<b>AbstractSet</b>
определяет только один абстрактный метод,
<b>contains().</b>
Любой класс, который претендует на роль множества, должен будет определить хотя бы один этот метод. Далее в примере определяется класс
<b>AbstractEnumerableSet</b>
, наследующий класс
<b>AbstractSet</b>
. Этот класс определяет абстрактные методы
<b>size()</b>
and
<b>foreach()</b>
и реализует конкретные вспомогательные методы (
<b>toString(), toArray(), equals()</b>
и т.д.).
<b>AbstractEnumerableSet</b>
не определяет методы
<b>add()</b>
или
<b>remove()</b>
и представляет класс множеств, доступных только для чтения. Класс
<b>SingletonSet </b>
может быть реализован как конкретный подкласс. Наконец, в примере определяется класс
<b>AbstractWritableSet</b>
, наследующий
<b>AbstractEnumerableSet</b>
. Этот последний абстрактный класс определяет абстрактные методы
<b>add()</b>
и
<b>remove()</b>
и реализует конкретные методы, такие как
<b>union()</b>
и
<b>intersection(),</b>
использующие их. Класс
<b>AbstractWritableSet</b>
отлично подходит на роль суперкласса для наших классов
<b>Set</b>
и
<b>FilteredSet</b>
. Однако они не были добавлены в пример, а вместо них была включена новая конкретная реализация с именем
<b>ArraySet</b>
.
Пример 9.16 довольно объемен, но он заслуживает детального изучения. Обратите внимание, что для простоты создания подклассов в нем используется функция
<b>Function.prototype.extend().</b>
Пример 9.16. Иерархия абстрактных и конкретных классов множеств
<b>// Вспомогательная функция, которая может использоваться для определения</b>
<b>// любого абстрактного метода</b>
<b>function abstractmethod() { throw new Error("абстрактный метод"); }</b>
<b>/*</b>
<b> * Класс AbstractSet определяет единственный абстрактный метод, contains().</b>
<b>*/</b>
<b>function AbstractSet() {</b>
<b> throw new Error("Нельзя создать экземпляр абстрактного класса");</b>
<b>}</b>
<b>AbstractSet.prototype.contains = abstractmethod;</b>
<b>/*</b>
<b> * NotSet - конкретный подкласс класса AbstractSet.</b>
<b> * Элементами этого множества являются все значения, которые не являются</b>
<b> * элементами некоторого другого множества. Поскольку это множество</b>
<b> * определяется в терминах другого множества, оно не доступно для записи,</b>
<b> * а так как оно имеет бесконечное число элементов, оно недоступно для перечисления.</b>
<b> * Все, что позволяет этот класс, - это проверить принадлежность к множеству.</b>
<b> * Обратите внимание, что для определения этого подкласса используется метод</b>
<b> * Function.prototype.extendO, объявленный выше.</b>
<b>*/</b>
<b>var NotSet = AbstractSet.extend(</b>
<b> function NotSet(set) { this.set = set; },</b>
<b> {</b>
<b> contains: function(x) { return !this.set.contains(x); },</b>
<b> toString: function(x) { return "~" + this.set.toString(); },</b>
<b> equals: function(that) {</b>
<b> return that instanceof NotSet && this.set.equals(that.set);</b>
<b> }</b>
<b> }</b>
<b>);</b>
<b>/*</b>
<b> * AbstractEnumerableSet - абстрактный подкласс класса AbstractSet.</b>
<b> * Определяет абстрактные методы size() и foreach() и реализует конкретные</b>
<b> * методы isEmptyO. toArrayO, to[Locale]String() и equals().</b>
<b> * Подклассы, реализующие методы contains(), size() и foreach(),</b>
<b> * получают эти пять конкретных методов даром.</b>