JavaScript. Подробное руководство, 6-е издание, стр. 127
<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><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><b>Set</b><b>SingletonSet</b><b>Set</b><b>SingletonSet</b><b>Set</b>