Наверное, это самая трудная тема для программиста, приступающего к изучению Ruby. В данном разделе мы вкратце рассмотрим некоторые следствия, вытекающие из динамической природы языка.
1.4.1. Кодирование во время выполнения
Мы уже упоминали директивы
load
и
require
. Важно понимать, что это не встроенные предложения и не управляющие конструкции; на самом деле это методы. Поэтому их можно вызывать, передавая переменные или выражения как параметры, в том числе условно. Сравните с директивой
#include
в языках С и C++, которая обрабатывается
во время компиляции.
Код можно строить и интерпретировать по частям. В качестве несколько искусственного примера рассмотрим приведенный ниже метод
calculate
и вызывающий его код:
def calculate(op1, operator, op2)
string = op1.to_s + operator + op2.to_s
# Предполагается, что operator - строка; построим длинную
Вот та же идея, доведенная чуть ли не до абсурда: программа запрашивает у пользователя имя метода и одну строку кода. Затем этот метод определяется и вызывается:
Зачастую необходимо написать программу, которая могла бы работать на разных платформах или при разных условиях, но при этом сохранить общий набор исходных текстов. Для этого в языке С применяются директивы
#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
Можно получить полный список всех методов, которые можно вызвать для данного объекта. Для этого предназначен метод