Говорят, что расширение классов ядра (monkey-patching) — это зло. Тут еще некоторые ведут споры касательно того, что считать monkey-patching’ом — добавление новых методов или переопределение старых, но мы обойдем этот спор стороной и сконцентрируемся на том, как это делать безболезненно.
module MyArrayExt
# ArrayMethods хранит методы для расширения Array
module ArrayMethods
def self.mid(array)
if array.any? { |elem| !["Fixnum", "Float"].include?(elem.class.to_s) }
raise NotNumericArrayError
else
array.inject(0) { |sum, elem| sum += elem }.to_f / array.size
end
end
def self.odd_elems(array)
indexes = []
(array.size / 2).times { |n| indexes << n * 2 + 1}
return indexes.map { |i| array[i] }
end
def self.even_elems(array)
indexes = []
(array.size / 2.0 + 0.5).to_i.times { |n| indexes << n * 2 }
return indexes.map { |i| array[i] }
end
class NotNumericArrayError < StandardError
end
end
# Delegator хранит ссылку на объект - массив
# и делегирует вызов методов модулю ArrayMethods
class Delegator
attr_accessor :obj
# Объявляем делегирующие методы, а можно было бы использовать
# method_missing с динамической делигацией, но так быстрее
ArrayMethods.singleton_methods.each do |method_name|
define_method(method_name) { ArrayMethods.send(method_name, obj) }
end
end
# Так мы создаем объект Delegator и
# присваиваем ему ссылку на объект - массив
def self.delegator_obj=(obj)
@delegator ||= Delegator.new
@delegator.obj = obj
end
def self.delegator
@delegator
end
# коллбек срабатывающий при extend'e модуля MyArrayExt
def self.extended(klass)
klass.class_eval do
# создаем метод Array#ext который возвращает объект
# делегатора и через который вызываем методы расширения
define_method :ext do
MyArrayExt.delegator_obj = self
MyArrayExt.delegator
end
end
end
end
В действии:
Array.extend MyArrayExt
[1,2,3].ext.mid #=> 2.0
[1,2,3,6,9,100,678,345].ext.mid #=> 143.0
[1,2,3,6,9,100,678,345.1].ext.mid #=> 143.0125
[1,2,3,6,9,100,678,345.1].ext.odd_elems #=> [2, 6, 100, 345.1]
[1,2,3,6,9,100,678,345.1].ext.even_elems #=> [1, 3, 9, 678]
[1,2,3,6,9,"ololo",678,345.1].ext.mid
# => MyArrayExt::ArrayMethods::NotNumericArrayError: MyArrayExt::ArrayMethods::NotNumericArrayError