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 или любому другому). Такое редко бывает оправданно, но если вы можете сделать

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату