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; і &lt; arguments.length; i++)</b>

<b>          if (arguments[i] == null)</b>

<b>            throw new Еrror(&quot;Нельзя добавить null или undefined&quot;);</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; і &lt; arguments.length; i++) {</b>

<b>          var v = arguments[i];</b>

<b>          if (!this.filter(v))</b>

<b>            throw new Error(&quot;FilteredSet: значение &quot; + v + &quot; отвергнуто фильтром&quot;);</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>
, а не быть его потомком.