Говорят, что расширение классов ядра (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