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); данный метод годится также для отслеживания использования модулей.
11.3.14. Определение чистильщиков для объектов
У классов в Ruby есть конструкторы (методы new
и initialize
), но нет деструкторов (методов, которые уничтожают объекты). Объясняется это тем, что в Ruby применяется алгоритм пометки и удаления объектов, на которые не осталось ссылок (сборка мусора); вот почему деструктор просто не имеет смысла.
Однако тем, кто переходит на Ruby с таких языков, как C++, этот механизм представляется необходимым — часто задается вопрос, как написать код очистки уничтожаемых объектов. Простой ответ звучит так: невозможно сделать это надежно. Но можно написать код, который будет вызываться, когда сборщик мусора уничтожает объект.
а = 'hello'
puts 'Для строки 'hello' ИД объекта равен #{a.id}.'
ObjectSpace.define_finalizer(а) { |id| puts 'Уничтожается #{id}.' }
puts 'Нечего убирать.'
GC.start
a = nil
puts 'Исходная строка - кандидат на роль мусора.'
GC.start
Этот код выводит следующее:
Для строки 'hello' ИД объекта равен 537684890.
Нечего убирать.
Исходная строка - кандидат на роль мусора.
Уничтожается 537684890.
Подчеркнем, что к моменту вызова чистильщика объект уже фактически уничтожен. Попытка преобразовать идентификатор в ссылку на объект с помощью метода ObjectSpace._id2ref
приведет к исключению RangeError
с сообщением о том, что вы пытаетесь воспользоваться уничтоженным объектом.
Имейте в виду, что в Ruby применяется консервативный вариант сборки мусора по алгоритму пометки и удаления. Нет гарантии, что любой объект будет убран до завершения программы.
Однако все это может оказаться и ненужным. В Ruby существует стиль программирования, в котором для инкапсуляции работы с ресурсами служат блоки. В конце блока ресурс освобождается, и жизнь продолжается без помощи чистильщиков. Рассмотрим, например, блочную форму метода File.open
:
File.open('myfile.txt') do |file|
line1 = file.read
# ...
end
Здесь в блок передается объект File
, а по выходе из блока файл закрывается, причем все это делается под контролем метода open
. Функциональное подмножество метода File.open
на чистом Ruby (сейчас этот метод ради эффективности написан на С) могло бы выглядеть так:
def File.open(name, mode = 'r')
f = os_file_open(name, mode)
if block_given?
begin
yield f
ensure
f.close
end
return nil
else