Выбрать главу

Код можно строить и интерпретировать по частям. В качестве несколько искусственного примера рассмотрим приведенный ниже метод calculate и вызывающий его код:

def calculate(op1, operator, op2)

 string = op1.to_s + operator + op2.to_s

 # Предполагается, что operator - строка; построим длинную

 # строку, состоящую из оператора и операндов.

 eval(string)                    # Вычисляем и возвращаем значение.

end

@alpha = 25

@beta = 12

puts calculate(2, "+",2)         # Печатается 4

puts calculate(5, "*", "@alpha") # Печатается 125

puts calculate("@beta", "**", 3) # Печатается 1728

Вот та же идея, доведенная чуть ли не до абсурда: программа запрашивает у пользователя имя метода и одну строку кода. Затем этот метод определяется и вызывается:

puts "Имя метода: "

meth_name = gets

puts "Строка кода: "

code = gets

string = %[def #{meth_name}\n #{code}\n end] # Строим строку.

eval(string) # Определяем метод.

eval(meth_name) # Вызываем метод.

Зачастую необходимо написать программу, которая могла бы работать на разных платформах или при разных условиях, но при этом сохранить общий набор исходных текстов. Для этого в языке С применяются директивы #ifdef, но в Ruby все определения исполняются. Не существует такого понятия, как «этап компиляции»; все конструкции динамические, а не статические. Поэтому для принятия решения такого рода мы можем просто вычислить условие во время выполнения:

if platform == Windows

 action1

elsif platform == Linux

 action2

else

 default_action

end

Конечно, за такое кодирование приходится расплачиваться некоторым снижением производительности, поскольку иногда условие вычисляется много раз. Но рассмотрим следующий пример, который делает практически то же самое, однако весь платформенно-зависимый код помещен в один метод, имя которого от платформы не зависит:

if platform == Windows

 def my_action

  action1

 end

 elsif platform == Linux

 def my_action

  action2

 end

 else

 def my_action

  default_action

 end

end

Таким способом мы достигаем желаемого результата, но условие вычисляется только один раз. Когда программа вызовет метод my_action, он уже будет правильно определен.

1.4.2. Отражение

В языках Smalltalk, LISP и Java реализована (с разной степенью полноты) идея рефлексивного программирования — активная среда может опрашивать структуру объектов и расширять либо модифицировать их во время выполнения.

В языке Ruby имеется развитая поддержка отражения, но все же он не заходит так далеко, как Smalltalk, где даже управляющие конструкции являются объектами. В Ruby управляющие конструкции и блоки не представляют собой объекты. (Объект Proc можно использовать для того, чтобы представить блок в виде объекта, но управляющие конструкции объектами не бывают никогда.)

Для определения того, используется ли идентификатор с данным именем, служит ключевое слово defined? (обратите внимание на вопросительный знак в конце слова):

if defined? some_var

 puts "some_var = #{some_var}"

else

 puts "Переменная some_var неизвестна."

end

Аналогично метод respond_to? выясняет, может ли объект отвечать на вызов указанного метода (то есть определен ли данный метод для данного объекта). Метод respond_to? определен в классе Object.

В Ruby запрос информации о типе во время выполнения поддерживается очень полно. Тип или класс объекта можно определить, воспользовавшись методом type (из класса Object). Метод is_a? сообщает, принадлежит ли объект некоторому классу (включая и его суперклассы); синонимом служит имя kind_of?. Например:

puts "abc".class "" # Печатается String

puts 345.class # Печатается Fixnum

rover = Dog.new

print rover.class # Печатается Dog

if rover.is_a? Dog

 puts "Конечно, является."

end

if rover.kind_of? Dog

 puts "Да, все еще собака."

end

if rover.is_a? Animal

 puts "Да, он к тому же и животное."

end

Можно получить полный список всех методов, которые можно вызвать для данного объекта. Для этого предназначен метод methods из класса Object. Имеются также его варианты private_instance_methods, public_instance_methods и т.д.