<b>var NonNullSet = (function() { // Определить и вызвать функцию</b>
<b> var superclass = Set; // Имя суперкласса указывается в одном месте,</b>
<b> return superclass.extend(</b>
<b> function() { superclass.apply(this, arguments); }, // конструктор </b>
<b> { // методы</b>
<b> add: function() {</b>
<b> // Проверить аргументы на равенство null или undefined</b>
<b> for(var і = 0; і < arguments.length; i++)</b>
<b> if (arguments[i] == null)</b>
<b> throw new Еrror("Нельзя добавить null или undefined");</b>
<b> // Вызвать метод базового класса, чтобы выполнить добавление</b>
<b> return superclass.prototype.add.apply(this, arguments);</b>
<b> }</b>
<b> });</b>
<b>}());</b>
В заключение хотелось бы подчеркнуть, что возможность создания подобных фабрик классов обусловлена динамической природой языка JavaScript. Фабрики классов представляют собой мощный и гибкий инструмент, не имеющий аналогов в языках, подобных Java и C++.
9.7.3. Композиция в сравнении с наследованием
В предыдущем разделе мы решили определить класс множеств, накладывающий ограничения на свои элементы в соответствии с некоторыми критериями, а для достижения поставленной цели использовали механизм наследования, определив функцию создания специализированного подкласса указанной реализации множества, использующего указанную функцию-фильтр для ограничения круга допустимых элементов множества. При таком подходе для каждой комбинации суперкласса и функции-фильтра необходимо создавать новый класс.
Однако существует более простой путь решения этой задачи. В объектно-ориентированном программировании существует известный принцип «предпочтения композиции перед наследованием».[18]
В данном случае применение приема композиции могло бы выглядеть как реализация нового множества, «обертывающего» другой объект множества и делегирующего ему все обращения после фильтрации нежелательных элементов. Пример 9.15 демонстрирует, как это реализовать.
Пример 9.15. Композиция множеств вместо наследования
<b>/*</b>
<b> * Объект FilteredSet обертывает указанный объект множества и применяет</b>
<b> * указанный фильтр в своем методе add(). Обращения ко всем остальным базовым</b>
<b> * методам просто передаются обернутому экземпляру множества.</b>
<b>*/</b>
<b>var FilteredSet = Set.extend(</b>
<b> function FilteredSet(set, filter) { // Конструктор</b>
<b> this.set = set; this.filter = filter;</b>
<b> },</b>
<b> { // Методы экземпляров</b>
<b> add: function() {</b>
<b> // Если фильтр был указан, применить его</b>
<b> if (this.filter) {</b>
<b> for(var і = 0; і < arguments.length; i++) {</b>
<b> var v = arguments[i];</b>
<b> if (!this.filter(v))</b>
<b> throw new Error("FilteredSet: значение " + v + " отвергнуто фильтром");</b>
<b> }</b>
<b> }</b>
<b> // Затем вызвать метод add() объекта</b>
<b> this.set.add() this.set.add.apply(this.set, arguments);</b>
<b> return this;</b>
<b> },</b>
<b> // Остальные методы просто вызывают соответствующие</b>
<b> // методы объекта this.set и ничего более,</b>
<b> remove: function() {</b>
<b> this.set.remove.apply(this.set, arguments);</b>
<b> return this;</b>
<b> }.</b>
<b> contains: function(v) {</b>
<b>return this.set.contains(v);},</b>
<b> size: function() { return this.set.size(); },</b>
<b> foreach: function(f,c) { this.set.foreach(f.c); }</b>
<b> }</b>
<b>)</b>
Одно из преимуществ применения приема композиции в данном случае заключается в том, что требуется определить только один подкласс
<b>FilteredSe</b>
t. Экземпляры этого класса могут накладывать ограничения на элементы любого другого эк» земпляра множества. Например, вместо класса
<b>NonNullSet</b>
, представленного выше, реализовать подобные ограничения можно было бы так:
<b>var s = new FilteredSet(new Set(), function(x) { return x !== null; });</b>
Можно даже наложить еще один фильтр на фильтрованное множество:
<b>var t = new FilteredSet(s, { function(x) { return !(x instanceof Set); });</b>
9.7.4. Иерархии классов и абстрактные классы
В предыдущем разделе было предложено «предпочесть композицию наследованию». Но для иллюстрации этого принципа мы создали подкласс класса
<b>Set</b>
. Сделано это было для того, чтобы получившийся подкласс был
<b>instanceof Set</b>
и наследовал полезные методы класса
<b>Set</b>
, такие как
<b>toString()</b>
и
<b>equals().</b>
Это достаточно уважительные причины, но, тем не менее, было бы неплохо иметь возможность использовать прием композиции без необходимости наследовать некоторую определенную реализацию множества, такую как класс
<b>Set</b>
. Аналогичный подход можно было бы использовать и при создании класса
<b>SingletonSet</b>
(пример 9.12) -этот класс был определен как подкласс класса
<b>Set</b>
, чтобы унаследовать вспомогательные методы, но его реализация существенно отличается от реализации суперкласса. Класс
<b>SingletonSet</b>
- это не специализированная версия класса
<b>Set</b>
, а совершенно иной тип множеств. В иерархии классов
<b>SingletonSet</b>
должен был бы находиться на одном уровне с классом
<b>Set</b>
, а не быть его потомком.