Программирование на языке Ruby
Шрифт:
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.
Листинг 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."
end
В результате выводится следующая информация:
Добавлен метод singleton_method_added в класс MyClass.
Добавлен метод meth1 в класс MyClass.
Добавлен метод meth2 в класс MyClass.
Отметим, что фактически добавлено три метода. Возможно, это противоречит вашим ожиданиям, но метод
singleton_method_added
может отследить и добавление самого себя. Метод
inherited
(из Class
) используется примерно так же. Он вызывается в момент создания подкласса.
class MyClass
def MyClass.inherited(subclass)
puts "#{subclass} наследует MyClass."
end
# ...
end
class OtherClass < MyClass
# ...
end
# Выводится: OtherClass наследует MyClass.
Можно также следить за добавлением методов экземпляра модуля к объекту (с помощью метода
extend
). При каждом выполнении extend вызывается метод extend_object
.
module MyMod
def MyMod.extend_object(obj)
puts "Расширяется объект id #{obj.object_id}, класс #{obj.class}"
super
end
# ...
end
x = [1, 2, 3]
x.extend(MyMod)
# Выводится:
# Расширяется объект id 36491192, класс Array
Поделиться с друзьями: