разрабатываем модуль, который можно включить в определение любого класса. С момента включения будут трассироваться любые обращения к методам этого класса. Что-то в этом роде:
class MyClass
include Tracing
def one
end
def two(x, y)
end
end
m = MyClass.new
m.one # Вызван метод one. Параметры =
m.two(1, 'cat') # Вызван метод two. Параметры = 1, cat
Он должен работать также для всех подклассов трассируемого класса:
class Fred < MyClass
def meth(*a)
end
end
Fred.new.meth{2,3,4,5) # вызван метод meth. Параметры =2, 3, 4, 5
Возможная реализация такого модуля показана в листинге 11.18.
module Tracing
def Tracing.included(into)
into.instance_methods(false).each { |m|
Tracing.hook_method(into, m) }
def into.method_added(meth)
unless @adding
@adding = true
Tracing.hook_method(self, meth)
@adding = false
end
end
end
def Tracing.hook_method(klass, meth)
klass.class_eval do
alias_method 'old_#{meth}', '#{meth}'
define_method(meth) do |*args|
puts 'Вызван метод #{meth}. Параметры = #{args.join(', ')}'
self.send('old_#{meth}',*args)
end
end
end
end
class MyClass
include Tracing
def first_meth
end
def second_meth(x, y)
end
end
m = MyClass.new
m.first_meth # Вызван метод first_meth. Параметры =
m.second_meth(1, 'cat') # Вызван метод second_meth. Параметры = 1, cat
В этом коде два основных метода. Первый, included
, вызывается при каждой вставке модуля в класс. Наша версия делает две вещи: вызывает метод hook_method
каждого метода, уже определенного в целевом классе, и вставляет определение метода method_added
в этот класс. В результате любой добавленный позже метод тоже будет обнаружен и для него вызван hook_method
. Сам метод hook_method
работает прямолинейно. При добавлении метода ему назначается синоним old_name
. Исходный метод заменяется кодом трассировки, который выводит имя и параметры метода, а затем вызывает метод, к которому было обращение.
Обратите внимание на использование конструкции alias_method
. Работает она почти так же, как alias
, но только для методов (да и сама является методом, а не ключевым словом). Можно было бы записать эту строку иначе:
# Еще два способа записать эту строку...
# Символы с интерполяцией:
alias_method :'old_#{meth}', :'#{meth}'
# Преобразование строк с помощью to_sym:
alias_method 'old_#{meth}'.to_sym, meth.to_sym
Чтобы обнаружить добавление нового метода класса в класс или модуль, можно определить метод класса singleton_method_added
внутри данного класса. (Напомним, что синглетный метод в этом смысле — то, что мы обычно называем методом класса, поскольку Class — это объект.) Этот метод определен в модуле Kernel
и по умолчанию ничего не делает, но мы можем переопределить его, как сочтем нужным.
class MyClass
def MyClass.singleton_method_added(sym)
puts 'Добавлен метод #{sym.to_s} в класс MyClass.'
end
def MyClass.meth1 puts 'Я meth1.'
end
end
def MyClass.meth2
puts 'А я meth2.'