Книга: Программирование на языке Ruby

11.3.13. Отслеживание изменений в определении класса или объекта

11.3.13. Отслеживание изменений в определении класса или объекта

А зачем, собственно? Кому интересны изменения, которым подвергался класс?

Одна возможная причина — желание следить за состоянием выполняемой программы на 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

Обращение к super необходимо для того, чтобы мог отработать исходный метод extend_object. Это напоминает поведение метода append_features (см. раздел 11.1.12); данный метод годится также для отслеживания использования модулей.

Оглавление книги


Генерация: 1.497. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз