Раздел «Язык Ruby».RubyCourseLecture04:
<<Метапрограммирование на Ruby

Следующая лекция
Предыдущая лекция

Лекция 4: ООП и контейнеры. Примесь Еnumerable

Давайте определим метод sum для массивов:

class Array
  def sum
    inject{|a,b| a+b}
  end
end

[1,2,3,4].sum #=> 10
['a', 'b', 'c'].sum #=> 'abc'

Мы здесь к существующему стандартному классу Array добавили новый метод. Изменение определенных классов называется monkey-patching (обезьяны тут не причем, правильнее этот термин называть партизанскими патчами - guerillas patching).

Метод sum было бы хорошо иметь для всех контейнеров. Мы можем написать такой цикл по классам:

require 'set'

[Set, Array, Range].each do |klass|
  klass.class_eval do
    def sum
      inject{|a,b| a+b}
    end
  end
end

[1,2,3,4].sum #=> 10
s = Set[1,2,1,1,2,3,1]
s.sum         #=> 6
(1..10).sum   # => 55

Но это неправильный подход. Правильно использовать примеси, которые являются альтернативой множественному наследованию.

module SumMixin
  def sum
    inject{|a,b| a+b}
  end
end

[Set, Array, Range].each do |klass|
  klass.class_eval do
    include SumMixin
  end
end

Но дело в том, что уже есть примесь Enumerable, которая включена во все стандартные классы контейнеров.

И правильное решение выглядит так:

module Enumerable
  def sum
    inject{|a,b| a+b}
  end
end

Чтобы подчеркнуть тот факт, что модуль Enumerable уже определен до нас и мы его лишь патчим, пишем так

Enumerable.module_eval do
  def sum
    inject{|a,b| a+b}
  end
end

Если бы модуль Enumerable не был определен, то последний код (в отличие от предыдущего) кинул бы Exception.

Важные утверждения:

ObjectSpace.each_object(Class) do |klass|
  puts klass if klass.include?(Enumerable)
end

Следующая лекция
Предыдущая лекция