klass = Object.const_get(classname)
x = klass.new(4, 1) # [1, 1, 1, 1]
А если имена вложены? Как выясняется, следующий код не работает:
class Alpha
class Beta
class Gamma
FOOBAR =237
end
end
end
str = 'Alpha::Beta::Gamma::FOOBAR'
val = Object.const_get(str) # Ошибка!
Дело в том, что метод const_get
недостаточно «умен», чтобы распознать такие вложенные имена. Впрочем, в следующем примере приведена работающая идиома:
# Структура класса та же
str = 'Alpha::Beta::Gamma::FOOBAR'
val = str.split('::').inject(Object) {|x,y| x.const_get(y) } # 237
Такой код встречается часто (и демонстрирует интересное применение inject
).
11.3.4. Получение и установка переменных экземпляра
Отвечая на пожелание употреблять eval
как можно реже, в Ruby теперь включены методы, которые могут получить или присвоить новое значение переменной экземпляра, имя которой задано в виде строки:
class MyClass
attr_reader :alpha, :beta
def initialize(a,b,g)
@alpha, @beta, @gamma = a, b, g
end
end
x = MyClass.new(10,11,12)
x.instance_variable_set('@alpha',234)
p x.alpha # 234
x.instance_variable_set('@gamma',345) # 345
v = x.instance_variable_get('@gamma') # 345
Прежде всего, отметим, что имя переменной должно начинаться со знака @
, иначе произойдет ошибка. Если это кажется вам неочевидным, вспомните, что метод attr_accessor
(и ему подобные) принимает для формирования имени метода символ, поэтому-то знак @
и опускается.
Не нарушает ли существование таких методов принцип инкапсуляции? Нет. Конечно, эти методы потенциально опасны. Пользоваться ими следует с осторожностью, а не при всяком удобном случае. Но нельзя говорить, что инкапсуляция нарушена, не видя, как эти инструменты применяются в конкретном случае. Если это делается обдуманно, ради ясно осознанной цели, то все хорошо. Если же цель состоит в том, чтобы нарушить проект или обойти неудачное проектное решение, это печально. Ruby намеренно предоставляет доступ к внутренним деталям объектов тем, кому это действительно нужно; ответственный программист не станет пользоваться свободой во вред.
11.3.5. Метод define_method
Помимо ключевого слова def
, единственный нормальный способ добавить метод в класс или объект — воспользоваться методом define_method
, причем он позволяет сделать это во время выполнения.
Конечно, в Ruby практически все происходит во время выполнения. Если окружить определение метода обращениями к puts
, как в примере ниже, вы это сами увидите.
class MyClass
puts 'до'
def meth
#...
end
puts 'после'
end
Но внутри тела метода или в другом аналогичном месте нельзя заново открыть класс (если только это не синглетный класс). В таком случае в прежних версиях Ruby приходилось прибегать к помощи eval
, теперь же у нас есть метод define_method
. Он принимает символ (имя метода) и блок (тело метода).
Первая (ошибочная) попытка воспользоваться этим методом могла бы выглядеть так:
# Не работает, так как метод define_method закрытый.
if today =~ /Saturday | Sunday/
define_method(:activity) { puts 'Отдыхаем!' }
else
define_method(:activity) { puts 'Работаем!' }
end
activity
Поскольку define_method
— закрытый метод, приходится поступать так:
# Работает (Object - это контекст верхнего уровня).
if today =~ /Saturday | Sunday/
Object.class_eval { define_method(:activity) { puts 'Отдыхаем!' } }
else
Object.class_eval { define_method(:activity) { puts 'Работаем!' } }
end
activity
Можно было бы поступить так же внутри определения класса (в применении к классу Object
или любому другому). Такое редко бывает оправданно, но если вы можете сделать